diff --git a/sdk/core/azure-core-tracing-opencensus/tests/asynctests/test_tracing_decorator_async.py b/sdk/core/azure-core-tracing-opencensus/tests/asynctests/test_tracing_decorator_async.py deleted file mode 100644 index 1f7a3eecdf35..000000000000 --- a/sdk/core/azure-core-tracing-opencensus/tests/asynctests/test_tracing_decorator_async.py +++ /dev/null @@ -1,209 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""The tests for decorators_async.py""" - -try: - from unittest import mock -except ImportError: - import mock - -import sys -import time - -import pytest -from azure.core.pipeline import Pipeline, PipelineResponse -from azure.core.pipeline.policies import HTTPPolicy -from azure.core.pipeline.transport import HttpTransport, HttpRequest -from azure.core.tracing.decorator import distributed_trace -from azure.core.tracing.decorator_async import distributed_trace_async -from azure.core.tracing.ext.opencensus_span import OpenCensusSpan -from opencensus.trace import tracer as tracer_module -from opencensus.trace.samplers import AlwaysOnSampler -from tracing_common import ContextHelper, MockExporter - - -class MockClient: - @distributed_trace - def __init__(self, policies=None, assert_current_span=False): - time.sleep(0.001) - self.request = HttpRequest("GET", "https://bing.com") - if policies is None: - policies = [] - policies.append(mock.Mock(spec=HTTPPolicy, send=self.verify_request)) - self.policies = policies - self.transport = mock.Mock(spec=HttpTransport) - self.pipeline = Pipeline(self.transport, policies=policies) - - self.expected_response = mock.Mock(spec=PipelineResponse) - self.assert_current_span = assert_current_span - - def verify_request(self, request): - if self.assert_current_span: - assert execution_context.get_current_span() is not None - return self.expected_response - - @distributed_trace_async - async def make_request(self, numb_times, **kwargs): - time.sleep(0.001) - if numb_times < 1: - return None - response = self.pipeline.run(self.request, **kwargs) - await self.get_foo(merge_span=True) - kwargs['merge_span'] = True - await self.make_request(numb_times - 1, **kwargs) - return response - - @distributed_trace_async - async def merge_span_method(self): - return await self.get_foo(merge_span=True) - - @distributed_trace_async - async def no_merge_span_method(self): - return await self.get_foo() - - @distributed_trace_async - async def get_foo(self): - time.sleep(0.001) - return 5 - - @distributed_trace_async(name_of_span="different name") - async def check_name_is_different(self): - time.sleep(0.001) - - @distributed_trace_async - async def raising_exception(self): - raise ValueError("Something went horribly wrong here") - - -@pytest.mark.asyncio -async def test_decorator_has_different_name(): - with ContextHelper(): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("overall"): - client = MockClient() - await client.check_name_is_different() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 2 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert parent.children[1].span_data.name == "different name" - - -@pytest.mark.skip(reason="Don't think this test makes sense anymore") -@pytest.mark.asyncio -async def test_with_nothing_imported(): - with ContextHelper(): - opencensus = sys.modules["opencensus"] - del sys.modules["opencensus"] - try: - client = MockClient(assert_current_span=True) - with pytest.raises(AssertionError): - await client.make_request(3) - finally: - sys.modules["opencensus"] = opencensus - - -@pytest.mark.skip(reason="Don't think this test makes sense anymore") -@pytest.mark.asyncio -async def test_with_opencensus_imported_but_not_used(): - with ContextHelper(): - client = MockClient(assert_current_span=True) - await client.make_request(3) - - -@pytest.mark.asyncio -async def test_with_opencencus_used(): - with ContextHelper(): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - parent = trace.start_span(name="OverAll") - client = MockClient(policies=[]) - await client.get_foo(parent_span=parent) - await client.get_foo() - parent.finish() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 3 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert not parent.children[0].children - assert parent.children[1].span_data.name == "MockClient.get_foo" - assert not parent.children[1].children - -@pytest.mark.parametrize("value", ["opencensus", None]) -@pytest.mark.asyncio -async def test_span_with_opencensus_merge_span(value): - with ContextHelper(tracer_to_use=value) as ctx: - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.start_span(name="OverAll") as parent: - client = MockClient() - await client.merge_span_method() - await client.no_merge_span_method() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 3 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert not parent.children[0].children - assert parent.children[1].span_data.name == "MockClient.merge_span_method" - assert not parent.children[1].children - assert parent.children[2].span_data.name == "MockClient.no_merge_span_method" - assert parent.children[2].children[0].span_data.name == "MockClient.get_foo" - - -@pytest.mark.parametrize("value", [None, "opencensus"]) -@pytest.mark.asyncio -async def test_span_with_opencensus_complicated(value): - with ContextHelper(tracer_to_use=value): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.start_span(name="OverAll") as parent: - client = MockClient() - await client.make_request(2) - with trace.span("child") as child: - time.sleep(0.001) - await client.make_request(2, parent_span=parent) - assert OpenCensusSpan.get_current_span() == child - await client.make_request(2) - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 4 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert not parent.children[0].children - assert parent.children[1].span_data.name == "MockClient.make_request" - assert not parent.children[1].children - assert parent.children[2].span_data.name == "child" - assert parent.children[2].children[0].span_data.name == "MockClient.make_request" - assert parent.children[3].span_data.name == "MockClient.make_request" - assert not parent.children[3].children - -@pytest.mark.parametrize("value", [None, "opencensus"]) -@pytest.mark.asyncio -async def test_span_with_exception(value): - """Assert that if an exception is raised, the next sibling method is actually a sibling span. - """ - with ContextHelper(tracer_to_use=value): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("overall"): - client = MockClient() - try: - await client.raising_exception() - except: - pass - await client.get_foo() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 3 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert parent.children[1].span_data.name == "MockClient.raising_exception" - # Exception should propagate status for Opencensus - assert parent.children[1].span_data.status.message == 'Something went horribly wrong here' - assert parent.children[2].span_data.name == "MockClient.get_foo" diff --git a/sdk/core/azure-core-tracing-opencensus/tests/conftest.py b/sdk/core/azure-core-tracing-opencensus/tests/conftest.py deleted file mode 100644 index f9bb5ef13940..000000000000 --- a/sdk/core/azure-core-tracing-opencensus/tests/conftest.py +++ /dev/null @@ -1,31 +0,0 @@ -# -------------------------------------------------------------------------- -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# The MIT License (MIT) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the ""Software""), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. -# -# -------------------------------------------------------------------------- -import sys - -# Ignore collection of async tests for Python 2 -collect_ignore = [] -if sys.version_info < (3, 5): - collect_ignore.append("asynctests") diff --git a/sdk/core/azure-core-tracing-opencensus/tests/test_settings.py b/sdk/core/azure-core-tracing-opencensus/tests/test_settings.py deleted file mode 100644 index 72a97cb50f53..000000000000 --- a/sdk/core/azure-core-tracing-opencensus/tests/test_settings.py +++ /dev/null @@ -1,49 +0,0 @@ -# -------------------------------------------------------------------------- -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# The MIT License (MIT) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the ""Software""), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# -------------------------------------------------------------------------- -import logging -import os -import sys -import pytest - -# module under test -import azure.core.settings as m - - -class TestConverters(object): - def test_convert_implementation(self): - opencensus = sys.modules["opencensus"] - del sys.modules["opencensus"] - try: - assert m.convert_tracing_impl(None) is None - assert m.convert_tracing_impl("opencensus") is not None - import opencensus - - assert m.convert_tracing_impl(None) is not None - assert m.convert_tracing_impl("opencensus") is not None - with pytest.raises(ValueError): - m.convert_tracing_impl("does not exist!!") - finally: - import opencensus diff --git a/sdk/core/azure-core-tracing-opencensus/tests/test_tracing_decorator.py b/sdk/core/azure-core-tracing-opencensus/tests/test_tracing_decorator.py deleted file mode 100644 index 453b09d5f902..000000000000 --- a/sdk/core/azure-core-tracing-opencensus/tests/test_tracing_decorator.py +++ /dev/null @@ -1,212 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""The tests for decorators.py and common.py""" - -try: - from unittest import mock -except ImportError: - import mock - -import sys -import time - -import pytest -from azure.core.pipeline import Pipeline, PipelineResponse -from azure.core.pipeline.policies import HTTPPolicy -from azure.core.pipeline.transport import HttpTransport, HttpRequest -from azure.core.settings import settings -from azure.core.tracing import common -from azure.core.tracing.decorator import distributed_trace -from azure.core.tracing.ext.opencensus_span import OpenCensusSpan -from opencensus.trace import tracer as tracer_module, execution_context -from opencensus.trace.samplers import AlwaysOnSampler -from tracing_common import ContextHelper, MockExporter - - -class MockClient: - @distributed_trace - def __init__(self, policies=None, assert_current_span=False): - time.sleep(0.001) - self.request = HttpRequest("GET", "https://bing.com") - if policies is None: - policies = [] - policies.append(mock.Mock(spec=HTTPPolicy, send=self.verify_request)) - self.policies = policies - self.transport = mock.Mock(spec=HttpTransport) - self.pipeline = Pipeline(self.transport, policies=policies) - - self.expected_response = mock.Mock(spec=PipelineResponse) - self.assert_current_span = assert_current_span - - def verify_request(self, request): - if self.assert_current_span: - assert execution_context.get_current_span() is not None - return self.expected_response - - @distributed_trace - def make_request(self, numb_times, **kwargs): - time.sleep(0.001) - if numb_times < 1: - return None - response = self.pipeline.run(self.request, **kwargs) - self.get_foo(merge_span=True) - kwargs['merge_span'] = True - self.make_request(numb_times - 1, **kwargs) - return response - - @distributed_trace - def merge_span_method(self): - return self.get_foo(merge_span=True) - - @distributed_trace - def no_merge_span_method(self): - return self.get_foo() - - @distributed_trace - def get_foo(self): - time.sleep(0.001) - return 5 - - @distributed_trace(name_of_span="different name") - def check_name_is_different(self): - time.sleep(0.001) - - @distributed_trace - def raising_exception(self): - raise ValueError("Something went horribly wrong here") - - -def random_function(): - pass - - -class TestCommon(object): - def test_get_function_and_class_name(self): - with ContextHelper(): - client = MockClient() - assert common.get_function_and_class_name(client.get_foo, client) == "MockClient.get_foo" - assert common.get_function_and_class_name(random_function) == "random_function" - - -class TestDecorator(object): - def test_decorator_has_different_name(self): - with ContextHelper(): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("overall"): - client = MockClient() - client.check_name_is_different() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 2 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert parent.children[1].span_data.name == "different name" - - @pytest.mark.skip(reason="Don't think this test makes sense anymore") - def test_with_nothing_imported(self): - with ContextHelper(): - opencensus = sys.modules["opencensus"] - del sys.modules["opencensus"] - try: - client = MockClient(assert_current_span=True) - with pytest.raises(AssertionError): - client.make_request(3) - finally: - sys.modules["opencensus"] = opencensus - - @pytest.mark.skip(reason="Don't think this test makes sense anymore") - def test_with_opencensus_imported_but_not_used(self): - with ContextHelper(): - client = MockClient(assert_current_span=True) - client.make_request(3) - - def test_with_opencencus_used(self): - with ContextHelper(): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - parent = trace.start_span(name="OverAll") - client = MockClient(policies=[]) - client.get_foo(parent_span=parent) - client.get_foo() - parent.finish() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 3 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert not parent.children[0].children - assert parent.children[1].span_data.name == "MockClient.get_foo" - assert not parent.children[1].children - - @pytest.mark.parametrize("value", ["opencensus", None]) - def test_span_with_opencensus_merge_span(self, value): - with ContextHelper(tracer_to_use=value) as ctx: - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.start_span(name="OverAll") as parent: - client = MockClient() - client.merge_span_method() - client.no_merge_span_method() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 3 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert not parent.children[0].children - assert parent.children[1].span_data.name == "MockClient.merge_span_method" - assert not parent.children[1].children - assert parent.children[2].span_data.name == "MockClient.no_merge_span_method" - assert parent.children[2].children[0].span_data.name == "MockClient.get_foo" - - @pytest.mark.parametrize("value", ["opencensus", None]) - def test_span_with_opencensus_complicated(self, value): - with ContextHelper(tracer_to_use=value) as ctx: - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.start_span(name="OverAll") as parent: - client = MockClient() - client.make_request(2) - with trace.span("child") as child: - time.sleep(0.001) - client.make_request(2, parent_span=parent) - assert OpenCensusSpan.get_current_span() == child - client.make_request(2) - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 4 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert not parent.children[0].children - assert parent.children[1].span_data.name == "MockClient.make_request" - assert not parent.children[1].children - assert parent.children[2].span_data.name == "child" - assert parent.children[2].children[0].span_data.name == "MockClient.make_request" - assert parent.children[3].span_data.name == "MockClient.make_request" - assert not parent.children[3].children - - @pytest.mark.parametrize("value", ["opencensus", None]) - def test_span_with_exception(self, value): - """Assert that if an exception is raised, the next sibling method is actually a sibling span. - """ - with ContextHelper(tracer_to_use=value): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("overall"): - client = MockClient() - try: - client.raising_exception() - except: - pass - client.get_foo() - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 3 - assert parent.children[0].span_data.name == "MockClient.__init__" - assert parent.children[1].span_data.name == "MockClient.raising_exception" - # Exception should propagate status for Opencensus - assert parent.children[1].span_data.status.message == 'Something went horribly wrong here' - assert parent.children[2].span_data.name == "MockClient.get_foo" diff --git a/sdk/core/azure-core-tracing-opencensus/tests/test_tracing_policy.py b/sdk/core/azure-core-tracing-opencensus/tests/test_tracing_policy.py deleted file mode 100644 index 57e467007432..000000000000 --- a/sdk/core/azure-core-tracing-opencensus/tests/test_tracing_policy.py +++ /dev/null @@ -1,169 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Tests for the distributed tracing policy.""" -import logging - -from azure.core.pipeline import PipelineResponse, PipelineRequest, PipelineContext -from azure.core.pipeline.policies import DistributedTracingPolicy, UserAgentPolicy -from azure.core.pipeline.transport import HttpRequest, HttpResponse -from opencensus.trace import tracer as tracer_module -from opencensus.trace.samplers import AlwaysOnSampler -from azure.core.tracing.ext.opencensus_span import OpenCensusSpan -from tracing_common import ContextHelper, MockExporter -import time -import pytest - - -def test_distributed_tracing_policy_solo(): - """Test policy with no other policy and happy path""" - with ContextHelper(): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("parent"): - policy = DistributedTracingPolicy() - - request = HttpRequest("GET", "http://127.0.0.1/temp?query=query") - request.headers["x-ms-client-request-id"] = "some client request id" - - pipeline_request = PipelineRequest(request, PipelineContext(None)) - policy.on_request(pipeline_request) - - response = HttpResponse(request, None) - response.headers = request.headers - response.status_code = 202 - response.headers["x-ms-request-id"] = "some request id" - - ctx = trace.span_context - header = trace.propagator.to_headers(ctx) - assert request.headers.get("traceparent") == header.get("traceparent") - - policy.on_response(pipeline_request, PipelineResponse(request, response, PipelineContext(None))) - time.sleep(0.001) - policy.on_request(pipeline_request) - policy.on_exception(pipeline_request) - - trace.finish() - exporter.build_tree() - parent = exporter.root - network_span = parent.children[0] - assert network_span.span_data.name == "/temp" - assert network_span.span_data.attributes.get("http.method") == "GET" - assert network_span.span_data.attributes.get("component") == "http" - assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1/temp?query=query" - assert network_span.span_data.attributes.get("http.user_agent") is None - assert network_span.span_data.attributes.get("x-ms-request-id") == "some request id" - assert network_span.span_data.attributes.get("x-ms-client-request-id") == "some client request id" - assert network_span.span_data.attributes.get("http.status_code") == 202 - - network_span = parent.children[1] - assert network_span.span_data.name == "/temp" - assert network_span.span_data.attributes.get("http.method") == "GET" - assert network_span.span_data.attributes.get("component") == "http" - assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1/temp?query=query" - assert network_span.span_data.attributes.get("x-ms-client-request-id") == "some client request id" - assert network_span.span_data.attributes.get("http.user_agent") is None - assert network_span.span_data.attributes.get("x-ms-request-id") == None - assert network_span.span_data.attributes.get("http.status_code") == 504 - - -def test_distributed_tracing_policy_badurl(caplog): - """Test policy with a bad url that will throw, and be sure policy ignores it""" - with ContextHelper(): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("parent"): - policy = DistributedTracingPolicy() - - request = HttpRequest("GET", "http://[[[") - request.headers["x-ms-client-request-id"] = "some client request id" - - pipeline_request = PipelineRequest(request, PipelineContext(None)) - with caplog.at_level(logging.WARNING, logger="azure.core.pipeline.policies.distributed_tracing"): - policy.on_request(pipeline_request) - assert "Unable to start network span" in caplog.text - - response = HttpResponse(request, None) - response.headers = request.headers - response.status_code = 202 - response.headers["x-ms-request-id"] = "some request id" - - ctx = trace.span_context - header = trace.propagator.to_headers(ctx) - assert request.headers.get("traceparent") is None # Got not network trace - - policy.on_response(pipeline_request, PipelineResponse(request, response, PipelineContext(None))) - time.sleep(0.001) - policy.on_request(pipeline_request) - policy.on_exception(pipeline_request) - - trace.finish() - exporter.build_tree() - parent = exporter.root - assert len(parent.children) == 0 - - -def test_distributed_tracing_policy_with_user_agent(): - """Test policy working with user agent.""" - with ContextHelper(environ={"AZURE_HTTP_USER_AGENT": "mytools"}): - exporter = MockExporter() - trace = tracer_module.Tracer(sampler=AlwaysOnSampler(), exporter=exporter) - with trace.span("parent"): - policy = DistributedTracingPolicy() - - request = HttpRequest("GET", "http://127.0.0.1") - request.headers["x-ms-client-request-id"] = "some client request id" - - pipeline_request = PipelineRequest(request, PipelineContext(None)) - - user_agent = UserAgentPolicy() - user_agent.on_request(pipeline_request) - policy.on_request(pipeline_request) - - response = HttpResponse(request, None) - response.headers = request.headers - response.status_code = 202 - response.headers["x-ms-request-id"] = "some request id" - pipeline_response = PipelineResponse(request, response, PipelineContext(None)) - - ctx = trace.span_context - header = trace.propagator.to_headers(ctx) - assert request.headers.get("traceparent") == header.get("traceparent") - - policy.on_response(pipeline_request, pipeline_response) - - time.sleep(0.001) - policy.on_request(pipeline_request) - try: - raise ValueError("Transport trouble") - except: - policy.on_exception(pipeline_request) - - user_agent.on_response(pipeline_request, pipeline_response) - - trace.finish() - exporter.build_tree() - parent = exporter.root - network_span = parent.children[0] - assert network_span.span_data.name == "/" - assert network_span.span_data.attributes.get("http.method") == "GET" - assert network_span.span_data.attributes.get("component") == "http" - assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1" - assert network_span.span_data.attributes.get("http.user_agent").endswith("mytools") - assert network_span.span_data.attributes.get("x-ms-request-id") == "some request id" - assert network_span.span_data.attributes.get("x-ms-client-request-id") == "some client request id" - assert network_span.span_data.attributes.get("http.status_code") == 202 - - network_span = parent.children[1] - assert network_span.span_data.name == "/" - assert network_span.span_data.attributes.get("http.method") == "GET" - assert network_span.span_data.attributes.get("component") == "http" - assert network_span.span_data.attributes.get("http.url") == "http://127.0.0.1" - assert network_span.span_data.attributes.get("http.user_agent").endswith("mytools") - assert network_span.span_data.attributes.get("x-ms-client-request-id") == "some client request id" - assert network_span.span_data.attributes.get("x-ms-request-id") is None - assert network_span.span_data.attributes.get("http.status_code") == 504 - # Exception should propagate status for Opencensus - assert network_span.span_data.status.message == 'Transport trouble' - diff --git a/sdk/core/azure-core-tracing-opentelemetry/HISTORY.md b/sdk/core/azure-core-tracing-opentelemetry/HISTORY.md new file mode 100644 index 000000000000..409a4e17d92e --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/HISTORY.md @@ -0,0 +1,10 @@ + +# Release History + +------------------- + +## 1.0.0b1 Unreleased + +### Features + +- Opentelemetry implementation of azure-core tracing protocol \ No newline at end of file diff --git a/sdk/core/azure-core-tracing-opentelemetry/MANIFEST.in b/sdk/core/azure-core-tracing-opentelemetry/MANIFEST.in new file mode 100644 index 000000000000..f008c7967322 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/MANIFEST.in @@ -0,0 +1,8 @@ +recursive-include tests *.py +include *.md +include azure/__init__.py +include azure/core/__init__.py +include azure/core/tracing/__init__.py +include azure/core/tracing/ext/__init__.py +recursive-include examples *.py + diff --git a/sdk/core/azure-core-tracing-opentelemetry/README.md b/sdk/core/azure-core-tracing-opentelemetry/README.md new file mode 100644 index 000000000000..528cc8c34687 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/README.md @@ -0,0 +1,85 @@ + + +# Azure Core Tracing OpenTelemetry client library for Python + +## Getting started + +Install the opentelemetry python for Python with [pip](https://pypi.org/project/pip/): + +```bash +pip install azure-core-tracing-opentelemetry --pre +``` + +Now you can use opentelemetry for Python as usual with any SDKs that are compatible +with azure-core tracing. This includes (not exhaustive list), azure-storage-blob, azure-keyvault-secrets, azure-eventhub, etc. + +## Key concepts + +* You don't need to pass any context, SDK will get it for you +* Those lines are the only ones you need to enable tracing + + ``` python + from azure.core.settings import settings + from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan + settings.tracing_implementation = OpenTelemetrySpan + ``` + +## Examples + +There is no explicit context to pass, you just create your usual opentelemetry tracer and +call any SDK code that is compatible with azure-core tracing. This is an example +using Azure Monitor exporter, but you can use any exporter (Zipkin, etc.). + +```python + +# Declare OpenTelemetry as enabled tracing plugin for Azure SDKs +from azure.core.settings import settings +from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan + +settings.tracing_implementation = OpenTelemetrySpan + +# Example of Azure Monitor exporter, but you can use anything OpenTelemetry supports +from opentelemetry.ext.azure_monitor import AzureMonitorSpanExporter +exporter = AzureMonitorSpanExporter( + instrumentation_key="uuid of the instrumentation key (see your Azure Monitor account)" +) + +# Regular open telemetry usage from here, see https://github.com/open-telemetry/opentelemetry-python +# for details +from opentelemetry import trace +from opentelemetry.sdk.trace import Tracer + +trace.set_preferred_tracer_implementation(lambda T: Tracer()) +tracer = trace.tracer() +tracer.add_span_processor( + SimpleExportSpanProcessor(exporter) +) + +# Example with Storage SDKs + +from azure.storage.blob import BlobServiceClient + +with tracer.start_as_current_span(name="MyApplication"): + client = BlobServiceClient.from_connection_string('connectionstring') + client.create_container('mycontainer') # Call will be traced +``` + +Azure Exporter can be found in the package `opentelemetry-azure-monitor-exporter` + + +## Troubleshooting + +This client raises exceptions defined in [Azure Core](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/core/azure-core/docs/exceptions.md). + + +## Next steps + +More documentation on OpenTelemetry configuration can be found on the [OpenTelemetry website](https://opentelemetry.io) + + +## Contributing +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/sdk/core/azure-core-tracing-opentelemetry/azure/__init__.py b/sdk/core/azure-core-tracing-opentelemetry/azure/__init__.py new file mode 100644 index 000000000000..0d1f7edf5dc6 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/azure/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore diff --git a/sdk/core/azure-core-tracing-opentelemetry/azure/core/__init__.py b/sdk/core/azure-core-tracing-opentelemetry/azure/core/__init__.py new file mode 100644 index 000000000000..0d1f7edf5dc6 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/azure/core/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore diff --git a/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/__init__.py b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/__init__.py new file mode 100644 index 000000000000..0d1f7edf5dc6 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore diff --git a/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/__init__.py b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/__init__.py new file mode 100644 index 000000000000..0d1f7edf5dc6 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/__init__.py @@ -0,0 +1 @@ +__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore diff --git a/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/__init__.py b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/__init__.py new file mode 100644 index 000000000000..851c33b3f62d --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/__init__.py @@ -0,0 +1,247 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Implements azure.core.tracing.AbstractSpan to wrap OpenTelemetry spans.""" + +from opentelemetry.trace import Span, Link, Tracer, SpanKind as OpenTelemetrySpanKind, tracer +from opentelemetry.context import Context +from opentelemetry.propagators import extract, inject + +from azure.core.tracing import SpanKind, HttpSpanMixin # pylint: disable=no-name-in-module + +from ._version import VERSION + +try: + from typing import TYPE_CHECKING +except ImportError: + TYPE_CHECKING = False + +if TYPE_CHECKING: + from typing import Dict, Optional, Union, Callable + + from azure.core.pipeline.transport import HttpRequest, HttpResponse + +__version__ = VERSION + + +def _get_headers_from_http_request_headers(headers: "Mapping[str, Any]", key: str): + """Return headers that matches this key. + + Must comply to opentelemetry.context.propagation.httptextformat.Getter: + Getter = typing.Callable[[_T, str], typing.List[str]] + """ + return [headers.get(key, "")] + + +def _set_headers_from_http_request_headers(headers: "Mapping[str, Any]", key: str, value: str): + """Set headers in the given headers dict. + + Must comply to opentelemetry.context.propagation.httptextformat.Setter: + Setter = typing.Callable[[_T, str, str], None] + """ + headers[key] = value + + +class OpenTelemetrySpan(HttpSpanMixin, object): + """OpenTelemetry plugin for Azure client libraries. + + :param span: The OpenTelemetry span to wrap, or nothing to create a new one. + :type span: ~OpenTelemetry.trace.Span + :param name: The name of the OpenTelemetry span to create if a new span is needed + :type name: str + """ + + def __init__(self, span=None, name="span"): + # type: (Optional[Span], Optional[str]) -> None + current_tracer = self.get_current_tracer() + self._span_instance = span or current_tracer.start_span(name=name) + self._current_ctxt_manager = None + + @property + def span_instance(self): + # type: () -> Span + """ + :return: The OpenTelemetry span that is being wrapped. + """ + return self._span_instance + + def span(self, name="span"): + # type: (Optional[str]) -> OpenCensusSpan + """ + Create a child span for the current span and append it to the child spans list in the span instance. + :param name: Name of the child span + :type name: str + :return: The OpenCensusSpan that is wrapping the child span instance + """ + return self.__class__(name=name) + + @property + def kind(self): + # type: () -> Optional[SpanKind] + """Get the span kind of this span.""" + value = self.span_instance.kind + return ( + SpanKind.CLIENT if value == OpenTelemetrySpanKind.CLIENT else + SpanKind.PRODUCER if value == OpenTelemetrySpanKind.PRODUCER else + SpanKind.SERVER if value == OpenTelemetrySpanKind.SERVER else + SpanKind.CONSUMER if value == OpenTelemetrySpanKind.CONSUMER else + SpanKind.INTERNAL if value == OpenTelemetrySpanKind.INTERNAL else + None + ) + + + @kind.setter + def kind(self, value): + # type: (SpanKind) -> None + """Set the span kind of this span.""" + kind = ( + OpenTelemetrySpanKind.CLIENT if value == SpanKind.CLIENT else + OpenTelemetrySpanKind.PRODUCER if value == SpanKind.PRODUCER else + OpenTelemetrySpanKind.SERVER if value == SpanKind.SERVER else + OpenTelemetrySpanKind.CONSUMER if value == SpanKind.CONSUMER else + OpenTelemetrySpanKind.INTERNAL if value == SpanKind.INTERNAL else + OpenTelemetrySpanKind.INTERNAL if value == SpanKind.UNSPECIFIED else + None + ) + if kind is None: + raise ValueError("Kind {} is not supported in OpenTelemetry".format(value)) + self.span_instance.kind = kind + + def __enter__(self): + """Start a span.""" + self.start() + self._current_ctxt_manager = self.get_current_tracer().use_span(self._span_instance, end_on_exit=True) + self._current_ctxt_manager.__enter__() + return self + + def __exit__(self, exception_type, exception_value, traceback): + """Finish a span.""" + if self._current_ctxt_manager: + self._current_ctxt_manager.__exit__(exception_type, exception_value, traceback) + self._current_ctxt_manager = None + + def start(self): + # type: () -> None + """Set the start time for a span.""" + self.span_instance.start() + + def finish(self): + # type: () -> None + """Set the end time for a span.""" + self.span_instance.end() + + def to_header(self): + # type: () -> Dict[str, str] + """ + Returns a dictionary with the header labels and values. + :return: A key value pair dictionary + """ + temp_headers = {} # type: Dict[str, str] + inject(self.get_current_tracer(), _set_headers_from_http_request_headers, temp_headers) + return temp_headers + + def add_attribute(self, key, value): + # type: (str, Union[str, int]) -> None + """ + Add attribute (key value pair) to the current span. + + :param key: The key of the key value pair + :type key: str + :param value: The value of the key value pair + :type value: str + """ + self.span_instance.set_attribute(key, value) + + def get_trace_parent(self): + """Return traceparent string as defined in W3C trace context specification. + + Example: + Value = 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 + base16(version) = 00 + base16(trace-id) = 4bf92f3577b34da6a3ce929d0e0e4736 + base16(parent-id) = 00f067aa0ba902b7 + base16(trace-flags) = 01 // sampled + + :return: a traceparent string + :rtype: str + """ + return self.to_header()['traceparent'] + + @classmethod + def link(cls, traceparent): + # type: (str) -> None + """ + Links the context to the current tracer. + + :param traceparent: A complete traceparent + :type traceparent: str + """ + cls.link_from_headers({ + 'traceparent': traceparent + }) + + @classmethod + def link_from_headers(cls, headers): + # type: (Dict[str, str]) -> None + """ + Given a dictionary, extracts the context and links the context to the current tracer. + + :param headers: A key value pair dictionary + :type headers: dict + """ + ctx = extract(_get_headers_from_http_request_headers, headers) + current_span = cls.get_current_span() + current_span.links.append(Link(ctx)) + + @classmethod + def get_current_span(cls): + # type: () -> Span + """ + Get the current span from the execution context. Return None otherwise. + """ + return cls.get_current_tracer().get_current_span() + + @classmethod + def get_current_tracer(cls): + # type: () -> Tracer + """ + Get the current tracer from the execution context. Return None otherwise. + """ + return tracer() + + @classmethod + def change_context(cls, span): + # type: (Span) -> ContextManager + """Change the context for the life of this context manager. + """ + return cls.get_current_tracer().use_span(span, end_on_exit=False) + + @classmethod + def set_current_span(cls, span): + # type: (Span) -> None + """Not supported by OpenTelemetry. + """ + raise NotImplementedError( + "set_current_span is not supported by OpenTelemetry plugin. Use change_context instead." + ) + + @classmethod + def set_current_tracer(cls, _): + # type: (Tracer) -> None + """ + Set the given tracer as the current tracer in the execution context. + :param tracer: The tracer to set the current tracer as + :type tracer: :class: OpenTelemetry.trace.Tracer + """ + # Do nothing, if you're able to get two tracer with OpenTelemetry that's a surprise! + + @classmethod + def with_current_context(cls, func): + # type: (Callable) -> Callable + """Passes the current spans to the new context the function will be run in. + + :param func: The function that will be run in the new context + :return: The target the pass in instead of the function + """ + return Context.with_current_context(func) diff --git a/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/_version.py b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/_version.py new file mode 100644 index 000000000000..ac9f392f513e --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/azure/core/tracing/ext/opentelemetry_span/_version.py @@ -0,0 +1,6 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +VERSION = "1.0.0b1" diff --git a/sdk/core/azure-core-tracing-opentelemetry/dev_requirements.txt b/sdk/core/azure-core-tracing-opentelemetry/dev_requirements.txt new file mode 100644 index 000000000000..8cd864f5defd --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/dev_requirements.txt @@ -0,0 +1,3 @@ +-e ../../../tools/azure-sdk-tools +../azure-core +opentelemetry-sdk>=0.3a0 \ No newline at end of file diff --git a/sdk/core/azure-core-tracing-opentelemetry/sdk_packaging.toml b/sdk/core/azure-core-tracing-opentelemetry/sdk_packaging.toml new file mode 100644 index 000000000000..e7687fdae93b --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/sdk_packaging.toml @@ -0,0 +1,2 @@ +[packaging] +auto_update = false \ No newline at end of file diff --git a/sdk/core/azure-core-tracing-opentelemetry/setup.cfg b/sdk/core/azure-core-tracing-opentelemetry/setup.cfg new file mode 100644 index 000000000000..3c6e79cf31da --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/sdk/core/azure-core-tracing-opentelemetry/setup.py b/sdk/core/azure-core-tracing-opentelemetry/setup.py new file mode 100644 index 000000000000..11e2d41247e8 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/setup.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +#-------------------------------------------------------------------------- + +import re +import os.path +from io import open +from setuptools import find_packages, setup # type: ignore + +# Change the PACKAGE_NAME only to change folder and different name +PACKAGE_NAME = "azure-core-tracing-opentelemetry" +PACKAGE_PPRINT_NAME = "Azure Core OpenTelemetry plugin" + +package_folder_path = "azure/core/tracing/ext/opentelemetry_span" + +# Version extraction inspired from 'requests' +with open(os.path.join(package_folder_path, '_version.py'), 'r') as fd: + version = re.search(r'^VERSION\s*=\s*[\'"]([^\'"]*)[\'"]', # type: ignore + fd.read(), re.MULTILINE).group(1) + +if not version: + raise RuntimeError('Cannot find version information') + +with open('README.md', encoding='utf-8') as f: + readme = f.read() +with open('HISTORY.md', encoding='utf-8') as f: + history = f.read() + +setup( + name=PACKAGE_NAME, + version=version, + description='Microsoft Azure {} Library for Python'.format(PACKAGE_PPRINT_NAME), + long_description=readme + '\n\n' + history, + long_description_content_type='text/markdown', + license='MIT License', + author='Microsoft Corporation', + author_email='azpysdkhelp@microsoft.com', + url='https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/core/azure-core-tracing-opentelemetry', + classifiers=[ + 'Development Status :: 4 - Beta', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'License :: OSI Approved :: MIT License', + ], + zip_safe=False, + packages=[ + 'azure.core.tracing.ext.opentelemetry_span', + ], + python_requires=">=3.5.0", + install_requires=[ + 'opentelemetry-api>=0.3a0', + 'azure-core<2.0.0,>=1.0.0', + ], + extras_require={ + ":python_version<'3.5'": ['typing'], + } +) diff --git a/sdk/core/azure-core-tracing-opentelemetry/tests/conftest.py b/sdk/core/azure-core-tracing-opentelemetry/tests/conftest.py new file mode 100644 index 000000000000..a834f86a40f6 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/tests/conftest.py @@ -0,0 +1,14 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from opentelemetry import trace +from opentelemetry.sdk.trace import Tracer + +import pytest + + +@pytest.fixture(scope="session") +def tracer(): + trace.set_preferred_tracer_implementation(lambda T: Tracer()) + return trace.tracer() diff --git a/sdk/core/azure-core-tracing-opentelemetry/tests/test_threading.py b/sdk/core/azure-core-tracing-opentelemetry/tests/test_threading.py new file mode 100644 index 000000000000..ffdea46be91f --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/tests/test_threading.py @@ -0,0 +1,26 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import threading + +from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan + + +def test_get_span_from_thread(tracer): + + result = [] + def get_span_from_thread(output): + current_span = OpenTelemetrySpan.get_current_span() + output.append(current_span) + + with tracer.start_as_current_span(name="TestSpan") as span: + + thread = threading.Thread( + target=OpenTelemetrySpan.with_current_context(get_span_from_thread), + args=(result,) + ) + thread.start() + thread.join() + + assert span is result[0] diff --git a/sdk/core/azure-core-tracing-opentelemetry/tests/test_tracing_implementations.py b/sdk/core/azure-core-tracing-opentelemetry/tests/test_tracing_implementations.py new file mode 100644 index 000000000000..b7a1297aec34 --- /dev/null +++ b/sdk/core/azure-core-tracing-opentelemetry/tests/test_tracing_implementations.py @@ -0,0 +1,164 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""The tests for opencensus_span.py""" + +import unittest + +try: + from unittest import mock +except ImportError: + import mock + +from opentelemetry.trace import SpanKind as OpenTelemetrySpanKind + +from azure.core.tracing.ext.opentelemetry_span import OpenTelemetrySpan +from azure.core.tracing import SpanKind +import os + +import pytest + + +class TestOpentelemetryWrapper: + def test_span_passed_in(self, tracer): + with tracer.start_as_current_span(name="parent") as parent: + wrapped_span = OpenTelemetrySpan(parent) + + assert wrapped_span.span_instance.name == "parent" + assert parent is tracer.get_current_span() + assert wrapped_span.span_instance is tracer.get_current_span() + + assert parent is tracer.get_current_span() + + def test_no_span_passed_in_with_no_environ(self, tracer): + with tracer.start_as_current_span("Root") as parent: + with OpenTelemetrySpan() as wrapped_span: + + assert wrapped_span.span_instance.name == "span" + assert wrapped_span.span_instance is tracer.get_current_span() + + assert parent is tracer.get_current_span() + + + def test_span(self, tracer): + with tracer.start_as_current_span("Root") as parent: + assert OpenTelemetrySpan.get_current_tracer() is tracer + with OpenTelemetrySpan() as wrapped_span: + assert wrapped_span.span_instance is tracer.get_current_span() + + with wrapped_span.span() as child: + assert child.span_instance.name == "span" + assert child.span_instance is tracer.get_current_span() + assert child.span_instance.parent is wrapped_span.span_instance + + def test_start_finish(self, tracer): + with tracer.start_as_current_span("Root") as parent: + wrapped_class = OpenTelemetrySpan() + assert wrapped_class.span_instance.start_time is not None + assert wrapped_class.span_instance.end_time is None + wrapped_class.start() + assert wrapped_class.span_instance.start_time is not None + assert wrapped_class.span_instance.end_time is None + wrapped_class.finish() + assert wrapped_class.span_instance.start_time is not None + assert wrapped_class.span_instance.end_time is not None + + def test_change_context(self, tracer): + with tracer.start_as_current_span("Root") as parent: + with OpenTelemetrySpan() as wrapped_class: + with OpenTelemetrySpan.change_context(parent): + assert tracer.get_current_span() is parent + + def test_to_header(self, tracer): + with tracer.start_as_current_span("Root") as parent: + wrapped_class = OpenTelemetrySpan() + headers = wrapped_class.to_header() + assert "traceparent" in headers + assert headers["traceparent"].startswith("00-") + + traceparent = wrapped_class.get_trace_parent() + assert traceparent.startswith("00-") + + assert traceparent == headers["traceparent"] + + def test_links(self, tracer): + with tracer.start_as_current_span("Root") as parent: + og_header = {"traceparent": "00-2578531519ed94423ceae67588eff2c9-231ebdc614cb9ddd-01"} + with OpenTelemetrySpan() as wrapped_class: + OpenTelemetrySpan.link_from_headers(og_header) + + assert len(wrapped_class.span_instance.links) == 1 + link = wrapped_class.span_instance.links[0] + + assert link.context.trace_id == int("2578531519ed94423ceae67588eff2c9", 16) + assert link.context.span_id == int("231ebdc614cb9ddd", 16) + + with OpenTelemetrySpan() as wrapped_class: + OpenTelemetrySpan.link("00-2578531519ed94423ceae67588eff2c9-231ebdc614cb9ddd-01") + + assert len(wrapped_class.span_instance.links) == 1 + link = wrapped_class.span_instance.links[0] + + assert link.context.trace_id == int("2578531519ed94423ceae67588eff2c9", 16) + assert link.context.span_id == int("231ebdc614cb9ddd", 16) + + + def test_add_attribute(self, tracer): + with tracer.start_as_current_span("Root") as parent: + wrapped_class = OpenTelemetrySpan(span=parent) + wrapped_class.add_attribute("test", "test2") + assert wrapped_class.span_instance.attributes["test"] == "test2" + assert parent.attributes["test"] == "test2" + + def test_set_http_attributes(self, tracer): + with tracer.start_as_current_span("Root") as parent: + wrapped_class = OpenTelemetrySpan(span=parent) + request = mock.Mock() + setattr(request, "method", "GET") + setattr(request, "url", "some url") + response = mock.Mock() + setattr(request, "headers", {}) + setattr(response, "status_code", 200) + wrapped_class.set_http_attributes(request) + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.CLIENT + assert wrapped_class.span_instance.attributes.get("http.method") == request.method + assert wrapped_class.span_instance.attributes.get("component") == "http" + assert wrapped_class.span_instance.attributes.get("http.url") == request.url + assert wrapped_class.span_instance.attributes.get("http.status_code") == 504 + assert wrapped_class.span_instance.attributes.get("http.user_agent") is None + request.headers["User-Agent"] = "some user agent" + wrapped_class.set_http_attributes(request, response) + assert wrapped_class.span_instance.attributes.get("http.status_code") == response.status_code + assert wrapped_class.span_instance.attributes.get("http.user_agent") == request.headers.get("User-Agent") + + def test_span_kind(self, tracer): + with tracer.start_as_current_span("Root") as parent: + wrapped_class = OpenTelemetrySpan(span=parent) + + wrapped_class.kind = SpanKind.UNSPECIFIED + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.INTERNAL + assert wrapped_class.kind == SpanKind.INTERNAL + + wrapped_class.kind = SpanKind.SERVER + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.SERVER + assert wrapped_class.kind == SpanKind.SERVER + + wrapped_class.kind = SpanKind.CLIENT + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.CLIENT + assert wrapped_class.kind == SpanKind.CLIENT + + wrapped_class.kind = SpanKind.PRODUCER + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.PRODUCER + assert wrapped_class.kind == SpanKind.PRODUCER + + wrapped_class.kind = SpanKind.CONSUMER + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.CONSUMER + assert wrapped_class.kind == SpanKind.CONSUMER + + wrapped_class.kind = SpanKind.INTERNAL + assert wrapped_class.span_instance.kind == OpenTelemetrySpanKind.INTERNAL + assert wrapped_class.kind == SpanKind.INTERNAL + + with pytest.raises(ValueError): + wrapped_class.kind = "somethingstuid" diff --git a/shared_requirements.txt b/shared_requirements.txt index be542b1a53a2..9c33d86e75cd 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -120,6 +120,7 @@ six>=1.6 opencensus>=0.6.0 opencensus-ext-threading opencensus-ext-azure>=0.3.1 +opentelemetry-api>=0.3a0 #override azure-eventhub-checkpointstoreblob-aio azure-storage-blob<13.0.0,>=12.0.0 #override azure-eventhub-checkpointstoreblob azure-storage-blob<13.0.0,>=12.0.0 #override azure-eventhub-checkpointstoreblob-aio aiohttp<4.0,>=3.0