mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-03 04:10:48 +08:00
Co-authored-by: Shalev Roda <65566801+shalevr@users.noreply.github.com> Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
@ -51,6 +51,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fix Flask instrumentation to only close the span if it was created by the same request context.
|
||||
([#1692](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1692))
|
||||
|
||||
### Changed
|
||||
- Update HTTP server/client instrumentation span names to comply with spec
|
||||
([#1759](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1759)
|
||||
|
||||
## Version 1.17.0/0.38b0 (2023-03-22)
|
||||
|
||||
### Added
|
||||
|
@ -179,7 +179,7 @@ def create_trace_config(
|
||||
return
|
||||
|
||||
http_method = params.method.upper()
|
||||
request_span_name = f"HTTP {http_method}"
|
||||
request_span_name = f"{http_method}"
|
||||
request_url = (
|
||||
remove_url_credentials(trace_config_ctx.url_filter(params.url))
|
||||
if callable(trace_config_ctx.url_filter)
|
||||
|
@ -119,7 +119,7 @@ class TestAioHttpIntegration(TestBase):
|
||||
self.assert_spans(
|
||||
[
|
||||
(
|
||||
"HTTP GET",
|
||||
"GET",
|
||||
(span_status, None),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -213,7 +213,7 @@ class TestAioHttpIntegration(TestBase):
|
||||
self.assert_spans(
|
||||
[
|
||||
(
|
||||
"HTTP GET",
|
||||
"GET",
|
||||
(StatusCode.UNSET, None),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -247,7 +247,7 @@ class TestAioHttpIntegration(TestBase):
|
||||
self.assert_spans(
|
||||
[
|
||||
(
|
||||
"HTTP GET",
|
||||
"GET",
|
||||
(expected_status, None),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -274,7 +274,7 @@ class TestAioHttpIntegration(TestBase):
|
||||
self.assert_spans(
|
||||
[
|
||||
(
|
||||
"HTTP GET",
|
||||
"GET",
|
||||
(StatusCode.ERROR, None),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -301,7 +301,7 @@ class TestAioHttpIntegration(TestBase):
|
||||
self.assert_spans(
|
||||
[
|
||||
(
|
||||
"HTTP GET",
|
||||
"GET",
|
||||
(StatusCode.ERROR, None),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -338,7 +338,7 @@ class TestAioHttpIntegration(TestBase):
|
||||
self.assert_spans(
|
||||
[
|
||||
(
|
||||
"HTTP GET",
|
||||
"GET",
|
||||
(StatusCode.UNSET, None),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -391,6 +391,7 @@ class TestAioHttpClientInstrumentor(TestBase):
|
||||
self.get_default_request(), self.URL, self.default_handler
|
||||
)
|
||||
span = self.assert_spans(1)
|
||||
self.assertEqual("GET", span.name)
|
||||
self.assertEqual("GET", span.attributes[SpanAttributes.HTTP_METHOD])
|
||||
self.assertEqual(
|
||||
f"http://{host}:{port}/test-path",
|
||||
|
@ -415,18 +415,23 @@ def set_status_code(span, status_code):
|
||||
|
||||
|
||||
def get_default_span_details(scope: dict) -> Tuple[str, dict]:
|
||||
"""Default implementation for get_default_span_details
|
||||
"""
|
||||
Default span name is the HTTP method and URL path, or just the method.
|
||||
https://github.com/open-telemetry/opentelemetry-specification/pull/3165
|
||||
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
|
||||
|
||||
Args:
|
||||
scope: the ASGI scope dictionary
|
||||
Returns:
|
||||
a tuple of the span name, and any attributes to attach to the span.
|
||||
"""
|
||||
span_name = (
|
||||
scope.get("path", "").strip()
|
||||
or f"HTTP {scope.get('method', '').strip()}"
|
||||
)
|
||||
|
||||
return span_name, {}
|
||||
path = scope.get("path", "").strip()
|
||||
method = scope.get("method", "").strip()
|
||||
if method and path: # http
|
||||
return f"{method} {path}", {}
|
||||
if path: # websocket
|
||||
return path, {}
|
||||
return method, {} # http with no path
|
||||
|
||||
|
||||
def _collect_target_attribute(
|
||||
|
@ -142,12 +142,12 @@ class TestAsgiApplication(AsgiTestBase):
|
||||
self.assertEqual(len(span_list), 4)
|
||||
expected = [
|
||||
{
|
||||
"name": "/ http receive",
|
||||
"name": "GET / http receive",
|
||||
"kind": trace_api.SpanKind.INTERNAL,
|
||||
"attributes": {"type": "http.request"},
|
||||
},
|
||||
{
|
||||
"name": "/ http send",
|
||||
"name": "GET / http send",
|
||||
"kind": trace_api.SpanKind.INTERNAL,
|
||||
"attributes": {
|
||||
SpanAttributes.HTTP_STATUS_CODE: 200,
|
||||
@ -155,12 +155,12 @@ class TestAsgiApplication(AsgiTestBase):
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "/ http send",
|
||||
"name": "GET / http send",
|
||||
"kind": trace_api.SpanKind.INTERNAL,
|
||||
"attributes": {"type": "http.response.body"},
|
||||
},
|
||||
{
|
||||
"name": "/",
|
||||
"name": "GET /",
|
||||
"kind": trace_api.SpanKind.SERVER,
|
||||
"attributes": {
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
@ -231,7 +231,7 @@ class TestAsgiApplication(AsgiTestBase):
|
||||
entry["name"] = span_name
|
||||
else:
|
||||
entry["name"] = " ".join(
|
||||
[span_name] + entry["name"].split(" ")[1:]
|
||||
[span_name] + entry["name"].split(" ")[2:]
|
||||
)
|
||||
return expected
|
||||
|
||||
@ -493,9 +493,9 @@ class TestAsgiApplication(AsgiTestBase):
|
||||
for entry in expected:
|
||||
if entry["kind"] == trace_api.SpanKind.SERVER:
|
||||
entry["name"] = "name from server hook"
|
||||
elif entry["name"] == "/ http receive":
|
||||
elif entry["name"] == "GET / http receive":
|
||||
entry["name"] = "name from client request hook"
|
||||
elif entry["name"] == "/ http send":
|
||||
elif entry["name"] == "GET / http send":
|
||||
entry["attributes"].update({"attr-from-hook": "value"})
|
||||
return expected
|
||||
|
||||
|
@ -173,18 +173,12 @@ class _DjangoMiddleware(MiddlewareMixin):
|
||||
match = resolve(request.path)
|
||||
|
||||
if hasattr(match, "route"):
|
||||
return match.route
|
||||
return f"{request.method} {match.route}"
|
||||
|
||||
# Instead of using `view_name`, better to use `_func_name` as some applications can use similar
|
||||
# view names in different modules
|
||||
if hasattr(match, "_func_name"):
|
||||
return match._func_name # pylint: disable=protected-access
|
||||
|
||||
# Fallback for safety as `_func_name` private field
|
||||
return match.view_name
|
||||
return request.method
|
||||
|
||||
except Resolver404:
|
||||
return f"HTTP {request.method}"
|
||||
return request.method
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def process_request(self, request):
|
||||
|
@ -150,9 +150,9 @@ class TestMiddleware(WsgiTestBase):
|
||||
|
||||
self.assertEqual(
|
||||
span.name,
|
||||
"^route/(?P<year>[0-9]{4})/template/$"
|
||||
"GET ^route/(?P<year>[0-9]{4})/template/$"
|
||||
if DJANGO_2_2
|
||||
else "tests.views.traced_template",
|
||||
else "GET",
|
||||
)
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
@ -177,9 +177,7 @@ class TestMiddleware(WsgiTestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(
|
||||
span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced"
|
||||
)
|
||||
self.assertEqual(span.name, "GET ^traced/" if DJANGO_2_2 else "GET")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
|
||||
@ -215,9 +213,7 @@ class TestMiddleware(WsgiTestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(
|
||||
span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced"
|
||||
)
|
||||
self.assertEqual(span.name, "POST ^traced/" if DJANGO_2_2 else "POST")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "POST")
|
||||
@ -241,9 +237,7 @@ class TestMiddleware(WsgiTestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(
|
||||
span.name, "^error/" if DJANGO_2_2 else "tests.views.error"
|
||||
)
|
||||
self.assertEqual(span.name, "GET ^error/" if DJANGO_2_2 else "GET")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.ERROR)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
|
||||
@ -307,9 +301,7 @@ class TestMiddleware(WsgiTestBase):
|
||||
span = span_list[0]
|
||||
self.assertEqual(
|
||||
span.name,
|
||||
"^span_name/([0-9]{4})/$"
|
||||
if DJANGO_2_2
|
||||
else "tests.views.route_span_name",
|
||||
"GET ^span_name/([0-9]{4})/$" if DJANGO_2_2 else "GET",
|
||||
)
|
||||
|
||||
def test_span_name_for_query_string(self):
|
||||
@ -323,9 +315,7 @@ class TestMiddleware(WsgiTestBase):
|
||||
span = span_list[0]
|
||||
self.assertEqual(
|
||||
span.name,
|
||||
"^span_name/([0-9]{4})/$"
|
||||
if DJANGO_2_2
|
||||
else "tests.views.route_span_name",
|
||||
"GET ^span_name/([0-9]{4})/$" if DJANGO_2_2 else "GET",
|
||||
)
|
||||
|
||||
def test_span_name_404(self):
|
||||
@ -334,7 +324,7 @@ class TestMiddleware(WsgiTestBase):
|
||||
self.assertEqual(len(span_list), 1)
|
||||
|
||||
span = span_list[0]
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
def test_traced_request_attrs(self):
|
||||
Client().get("/span_name/1234/", CONTENT_TYPE="test/ct")
|
||||
|
@ -137,7 +137,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(span.name, "^route/(?P<year>[0-9]{4})/template/$")
|
||||
self.assertEqual(span.name, "GET ^route/(?P<year>[0-9]{4})/template/$")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
|
||||
@ -160,7 +160,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(span.name, "^traced/")
|
||||
self.assertEqual(span.name, "GET ^traced/")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
|
||||
@ -195,7 +195,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(span.name, "^traced/")
|
||||
self.assertEqual(span.name, "POST ^traced/")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "POST")
|
||||
@ -218,7 +218,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
|
||||
span = spans[0]
|
||||
|
||||
self.assertEqual(span.name, "^error/")
|
||||
self.assertEqual(span.name, "GET ^error/")
|
||||
self.assertEqual(span.kind, SpanKind.SERVER)
|
||||
self.assertEqual(span.status.status_code, StatusCode.ERROR)
|
||||
self.assertEqual(span.attributes[SpanAttributes.HTTP_METHOD], "GET")
|
||||
@ -264,7 +264,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
self.assertEqual(len(span_list), 1)
|
||||
|
||||
span = span_list[0]
|
||||
self.assertEqual(span.name, "^span_name/([0-9]{4})/$")
|
||||
self.assertEqual(span.name, "GET ^span_name/([0-9]{4})/$")
|
||||
|
||||
async def test_span_name_for_query_string(self):
|
||||
"""
|
||||
@ -275,7 +275,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
self.assertEqual(len(span_list), 1)
|
||||
|
||||
span = span_list[0]
|
||||
self.assertEqual(span.name, "^span_name/([0-9]{4})/$")
|
||||
self.assertEqual(span.name, "GET ^span_name/([0-9]{4})/$")
|
||||
|
||||
async def test_span_name_404(self):
|
||||
await self.async_client.get("/span_name/1234567890/")
|
||||
@ -283,7 +283,7 @@ class TestMiddlewareAsgi(SimpleTestCase, TestBase):
|
||||
self.assertEqual(len(span_list), 1)
|
||||
|
||||
span = span_list[0]
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
async def test_traced_request_attrs(self):
|
||||
await self.async_client.get("/span_name/1234/", CONTENT_TYPE="test/ct")
|
||||
|
@ -145,7 +145,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
span = spans[0]
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET /does-not-exist")
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertSpanHasAttributes(
|
||||
span,
|
||||
|
@ -227,7 +227,7 @@ class FastAPIInstrumentor(BaseInstrumentor):
|
||||
app.add_middleware(
|
||||
OpenTelemetryMiddleware,
|
||||
excluded_urls=excluded_urls,
|
||||
default_span_details=_get_route_details,
|
||||
default_span_details=_get_default_span_details,
|
||||
server_request_hook=server_request_hook,
|
||||
client_request_hook=client_request_hook,
|
||||
client_response_hook=client_response_hook,
|
||||
@ -300,7 +300,7 @@ class _InstrumentedFastAPI(fastapi.FastAPI):
|
||||
self.add_middleware(
|
||||
OpenTelemetryMiddleware,
|
||||
excluded_urls=_InstrumentedFastAPI._excluded_urls,
|
||||
default_span_details=_get_route_details,
|
||||
default_span_details=_get_default_span_details,
|
||||
server_request_hook=_InstrumentedFastAPI._server_request_hook,
|
||||
client_request_hook=_InstrumentedFastAPI._client_request_hook,
|
||||
client_response_hook=_InstrumentedFastAPI._client_response_hook,
|
||||
@ -316,15 +316,21 @@ class _InstrumentedFastAPI(fastapi.FastAPI):
|
||||
|
||||
|
||||
def _get_route_details(scope):
|
||||
"""Callback to retrieve the fastapi route being served.
|
||||
"""
|
||||
Function to retrieve Starlette route from scope.
|
||||
|
||||
TODO: there is currently no way to retrieve http.route from
|
||||
a starlette application from scope.
|
||||
|
||||
See: https://github.com/encode/starlette/pull/804
|
||||
|
||||
Args:
|
||||
scope: A Starlette scope
|
||||
Returns:
|
||||
A string containing the route or None
|
||||
"""
|
||||
app = scope["app"]
|
||||
route = None
|
||||
|
||||
for starlette_route in app.routes:
|
||||
match, _ = starlette_route.matches(scope)
|
||||
if match == Match.FULL:
|
||||
@ -332,10 +338,27 @@ def _get_route_details(scope):
|
||||
break
|
||||
if match == Match.PARTIAL:
|
||||
route = starlette_route.path
|
||||
# method only exists for http, if websocket
|
||||
# leave it blank.
|
||||
span_name = route or scope.get("method", "")
|
||||
return route
|
||||
|
||||
|
||||
def _get_default_span_details(scope):
|
||||
"""
|
||||
Callback to retrieve span name and attributes from scope.
|
||||
|
||||
Args:
|
||||
scope: A Starlette scope
|
||||
Returns:
|
||||
A tuple of span name and attributes
|
||||
"""
|
||||
route = _get_route_details(scope)
|
||||
method = scope.get("method", "")
|
||||
attributes = {}
|
||||
if route:
|
||||
attributes[SpanAttributes.HTTP_ROUTE] = route
|
||||
if method and route: # http
|
||||
span_name = f"{method} {route}"
|
||||
elif route: # websocket
|
||||
span_name = route
|
||||
else: # fallback
|
||||
span_name = method
|
||||
return span_name, attributes
|
||||
|
@ -106,7 +106,7 @@ class TestFastAPIManualInstrumentation(TestBase):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 3)
|
||||
for span in spans:
|
||||
self.assertIn("/foobar", span.name)
|
||||
self.assertIn("GET /foobar", span.name)
|
||||
|
||||
def test_uninstrument_app(self):
|
||||
self._client.get("/foobar")
|
||||
@ -138,7 +138,7 @@ class TestFastAPIManualInstrumentation(TestBase):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 3)
|
||||
for span in spans:
|
||||
self.assertIn("/foobar", span.name)
|
||||
self.assertIn("GET /foobar", span.name)
|
||||
|
||||
def test_fastapi_route_attribute_added(self):
|
||||
"""Ensure that fastapi routes are used as the span name."""
|
||||
@ -146,7 +146,7 @@ class TestFastAPIManualInstrumentation(TestBase):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 3)
|
||||
for span in spans:
|
||||
self.assertIn("/user/{username}", span.name)
|
||||
self.assertIn("GET /user/{username}", span.name)
|
||||
self.assertEqual(
|
||||
spans[-1].attributes[SpanAttributes.HTTP_ROUTE], "/user/{username}"
|
||||
)
|
||||
|
@ -214,7 +214,7 @@ class TestProgrammatic(InstrumentationTest, WsgiTestBase):
|
||||
resp.close()
|
||||
span_list = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(span_list), 1)
|
||||
self.assertEqual(span_list[0].name, "HTTP POST")
|
||||
self.assertEqual(span_list[0].name, "POST /bye")
|
||||
self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER)
|
||||
self.assertEqual(span_list[0].attributes, expected_attrs)
|
||||
|
||||
|
@ -209,7 +209,7 @@ class ResponseInfo(typing.NamedTuple):
|
||||
|
||||
|
||||
def _get_default_span_name(method: str) -> str:
|
||||
return f"HTTP {method.strip()}"
|
||||
return method.strip()
|
||||
|
||||
|
||||
def _apply_status_code(span: Span, status_code: int) -> None:
|
||||
|
@ -142,7 +142,7 @@ class BaseTestCases:
|
||||
span = self.assert_span()
|
||||
|
||||
self.assertIs(span.kind, trace.SpanKind.CLIENT)
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
self.assertEqual(
|
||||
span.attributes,
|
||||
@ -258,7 +258,7 @@ class BaseTestCases:
|
||||
|
||||
span = self.assert_span()
|
||||
|
||||
self.assertEqual(span.name, "HTTP POST")
|
||||
self.assertEqual(span.name, "POST")
|
||||
self.assertEqual(
|
||||
span.attributes[SpanAttributes.HTTP_METHOD], "POST"
|
||||
)
|
||||
@ -350,7 +350,7 @@ class BaseTestCases:
|
||||
|
||||
self.assertEqual(result.text, "Hello!")
|
||||
span = self.assert_span()
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
def test_not_recording(self):
|
||||
with mock.patch("opentelemetry.trace.INVALID_SPAN") as mock_span:
|
||||
@ -444,7 +444,7 @@ class BaseTestCases:
|
||||
|
||||
self.assertEqual(result.text, "Hello!")
|
||||
span = self.assert_span()
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
HTTPXClientInstrumentor().uninstrument()
|
||||
|
||||
def test_not_recording(self):
|
||||
|
@ -145,7 +145,7 @@ class TestProgrammatic(InstrumentationTest, WsgiTestBase):
|
||||
resp.close()
|
||||
span_list = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(span_list), 1)
|
||||
self.assertEqual(span_list[0].name, "HTTP POST")
|
||||
self.assertEqual(span_list[0].name, "POST /bye")
|
||||
self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER)
|
||||
self.assertEqual(span_list[0].attributes, expected_attrs)
|
||||
|
||||
|
@ -245,8 +245,16 @@ def _uninstrument_from(instr_root, restore_as_bound_func=False):
|
||||
|
||||
|
||||
def get_default_span_name(method):
|
||||
"""Default implementation for name_callback, returns HTTP {method_name}."""
|
||||
return f"HTTP {method.strip()}"
|
||||
"""
|
||||
Default implementation for name_callback, returns HTTP {method_name}.
|
||||
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
|
||||
|
||||
Args:
|
||||
method: string representing HTTP method
|
||||
Returns:
|
||||
span name
|
||||
"""
|
||||
return method.strip()
|
||||
|
||||
|
||||
class RequestsInstrumentor(BaseInstrumentor):
|
||||
|
@ -116,7 +116,7 @@ class RequestsIntegrationTestBase(abc.ABC):
|
||||
span = self.assert_span()
|
||||
|
||||
self.assertIs(span.kind, trace.SpanKind.CLIENT)
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
self.assertEqual(
|
||||
span.attributes,
|
||||
@ -191,7 +191,7 @@ class RequestsIntegrationTestBase(abc.ABC):
|
||||
self.assertEqual(result.text, "Hello!")
|
||||
span = self.assert_span()
|
||||
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
def test_not_foundbasic(self):
|
||||
url_404 = "http://mock/status/404"
|
||||
|
@ -71,7 +71,7 @@ class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase):
|
||||
|
||||
span = self.assert_span()
|
||||
self.assertIs(trace.SpanKind.CLIENT, span.kind)
|
||||
self.assertEqual("HTTP GET", span.name)
|
||||
self.assertEqual("GET", span.name)
|
||||
|
||||
attributes = {
|
||||
"http.status_code": 200,
|
||||
|
@ -212,7 +212,7 @@ class StarletteInstrumentor(BaseInstrumentor):
|
||||
app.add_middleware(
|
||||
OpenTelemetryMiddleware,
|
||||
excluded_urls=_excluded_urls,
|
||||
default_span_details=_get_route_details,
|
||||
default_span_details=_get_default_span_details,
|
||||
server_request_hook=server_request_hook,
|
||||
client_request_hook=client_request_hook,
|
||||
client_response_hook=client_response_hook,
|
||||
@ -278,7 +278,7 @@ class _InstrumentedStarlette(applications.Starlette):
|
||||
self.add_middleware(
|
||||
OpenTelemetryMiddleware,
|
||||
excluded_urls=_excluded_urls,
|
||||
default_span_details=_get_route_details,
|
||||
default_span_details=_get_default_span_details,
|
||||
server_request_hook=_InstrumentedStarlette._server_request_hook,
|
||||
client_request_hook=_InstrumentedStarlette._client_request_hook,
|
||||
client_response_hook=_InstrumentedStarlette._client_response_hook,
|
||||
@ -294,15 +294,21 @@ class _InstrumentedStarlette(applications.Starlette):
|
||||
|
||||
|
||||
def _get_route_details(scope):
|
||||
"""Callback to retrieve the starlette route being served.
|
||||
"""
|
||||
Function to retrieve Starlette route from scope.
|
||||
|
||||
TODO: there is currently no way to retrieve http.route from
|
||||
a starlette application from scope.
|
||||
|
||||
See: https://github.com/encode/starlette/pull/804
|
||||
|
||||
Args:
|
||||
scope: A Starlette scope
|
||||
Returns:
|
||||
A string containing the route or None
|
||||
"""
|
||||
app = scope["app"]
|
||||
route = None
|
||||
|
||||
for starlette_route in app.routes:
|
||||
match, _ = starlette_route.matches(scope)
|
||||
if match == Match.FULL:
|
||||
@ -310,10 +316,27 @@ def _get_route_details(scope):
|
||||
break
|
||||
if match == Match.PARTIAL:
|
||||
route = starlette_route.path
|
||||
# method only exists for http, if websocket
|
||||
# leave it blank.
|
||||
span_name = route or scope.get("method", "")
|
||||
return route
|
||||
|
||||
|
||||
def _get_default_span_details(scope):
|
||||
"""
|
||||
Callback to retrieve span name and attributes from scope.
|
||||
|
||||
Args:
|
||||
scope: A Starlette scope
|
||||
Returns:
|
||||
A tuple of span name and attributes
|
||||
"""
|
||||
route = _get_route_details(scope)
|
||||
method = scope.get("method", "")
|
||||
attributes = {}
|
||||
if route:
|
||||
attributes[SpanAttributes.HTTP_ROUTE] = route
|
||||
if method and route: # http
|
||||
span_name = f"{method} {route}"
|
||||
elif route: # websocket
|
||||
span_name = route
|
||||
else: # fallback
|
||||
span_name = method
|
||||
return span_name, attributes
|
||||
|
@ -93,7 +93,7 @@ class TestStarletteManualInstrumentation(TestBase):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 3)
|
||||
for span in spans:
|
||||
self.assertIn("/foobar", span.name)
|
||||
self.assertIn("GET /foobar", span.name)
|
||||
|
||||
def test_starlette_route_attribute_added(self):
|
||||
"""Ensure that starlette routes are used as the span name."""
|
||||
@ -101,7 +101,7 @@ class TestStarletteManualInstrumentation(TestBase):
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 3)
|
||||
for span in spans:
|
||||
self.assertIn("/user/{username}", span.name)
|
||||
self.assertIn("GET /user/{username}", span.name)
|
||||
self.assertEqual(
|
||||
spans[-1].attributes[SpanAttributes.HTTP_ROUTE], "/user/{username}"
|
||||
)
|
||||
|
@ -454,10 +454,23 @@ def _get_attributes_from_request(request):
|
||||
)
|
||||
|
||||
|
||||
def _get_operation_name(handler, request):
|
||||
full_class_name = type(handler).__name__
|
||||
class_name = full_class_name.rsplit(".")[-1]
|
||||
return f"{class_name}.{request.method.lower()}"
|
||||
def _get_default_span_name(request):
|
||||
"""
|
||||
Default span name is the HTTP method and URL path, or just the method.
|
||||
https://github.com/open-telemetry/opentelemetry-specification/pull/3165
|
||||
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
|
||||
|
||||
Args:
|
||||
request: Tornado request object.
|
||||
Returns:
|
||||
Default span name.
|
||||
"""
|
||||
|
||||
path = request.path
|
||||
method = request.method
|
||||
if method and path:
|
||||
return f"{method} {path}"
|
||||
return f"{method}"
|
||||
|
||||
|
||||
def _get_full_handler_name(handler):
|
||||
@ -468,7 +481,7 @@ def _get_full_handler_name(handler):
|
||||
def _start_span(tracer, handler) -> _TraceContext:
|
||||
span, token = _start_internal_or_server_span(
|
||||
tracer=tracer,
|
||||
span_name=_get_operation_name(handler, handler.request),
|
||||
span_name=_get_default_span_name(handler.request),
|
||||
start_time=time_ns(),
|
||||
context_carrier=handler.request.headers,
|
||||
context_getter=textmap.default_getter,
|
||||
|
@ -136,7 +136,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(manual.parent, server.context)
|
||||
self.assertEqual(manual.context.trace_id, client.context.trace_id)
|
||||
|
||||
self.assertEqual(server.name, "MainHandler." + method.lower())
|
||||
self.assertEqual(server.name, f"{method} /")
|
||||
self.assertTrue(server.parent.is_remote)
|
||||
self.assertNotEqual(server.parent, client.context)
|
||||
self.assertEqual(server.parent.span_id, client.context.span_id)
|
||||
@ -197,7 +197,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(len(spans), 5)
|
||||
|
||||
client = spans.by_name("GET")
|
||||
server = spans.by_name(handler_name + ".get")
|
||||
server = spans.by_name(f"GET {url}")
|
||||
sub_wrapper = spans.by_name("sub-task-wrapper")
|
||||
|
||||
sub2 = spans.by_name("sub-task-2")
|
||||
@ -214,7 +214,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(sub_wrapper.parent, server.context)
|
||||
self.assertEqual(sub_wrapper.context.trace_id, client.context.trace_id)
|
||||
|
||||
self.assertEqual(server.name, handler_name + ".get")
|
||||
self.assertEqual(server.name, f"GET {url}")
|
||||
self.assertTrue(server.parent.is_remote)
|
||||
self.assertNotEqual(server.parent, client.context)
|
||||
self.assertEqual(server.parent.span_id, client.context.span_id)
|
||||
@ -230,6 +230,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
SpanAttributes.HTTP_TARGET: url,
|
||||
SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
|
||||
SpanAttributes.HTTP_STATUS_CODE: 201,
|
||||
"tornado.handler": f"tests.tornado_test_app.{handler_name}",
|
||||
},
|
||||
)
|
||||
|
||||
@ -254,9 +255,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(len(spans), 2)
|
||||
|
||||
client = spans.by_name("GET")
|
||||
server = spans.by_name("BadHandler.get")
|
||||
server = spans.by_name("GET /error")
|
||||
|
||||
self.assertEqual(server.name, "BadHandler.get")
|
||||
self.assertEqual(server.name, "GET /error")
|
||||
self.assertEqual(server.kind, SpanKind.SERVER)
|
||||
self.assertSpanHasAttributes(
|
||||
server,
|
||||
@ -291,7 +292,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(len(spans), 2)
|
||||
server, client = spans
|
||||
|
||||
self.assertEqual(server.name, "ErrorHandler.get")
|
||||
self.assertEqual(server.name, "GET /missing-url")
|
||||
self.assertEqual(server.kind, SpanKind.SERVER)
|
||||
self.assertSpanHasAttributes(
|
||||
server,
|
||||
@ -326,7 +327,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(len(spans), 2)
|
||||
server, client = spans
|
||||
|
||||
self.assertEqual(server.name, "RaiseHTTPErrorHandler.get")
|
||||
self.assertEqual(server.name, "GET /raise_403")
|
||||
self.assertEqual(server.kind, SpanKind.SERVER)
|
||||
self.assertSpanHasAttributes(
|
||||
server,
|
||||
@ -367,7 +368,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(len(spans), 2)
|
||||
server, client = spans
|
||||
|
||||
self.assertEqual(server.name, "DynamicHandler.get")
|
||||
self.assertEqual(server.name, "GET /dyna")
|
||||
self.assertTrue(server.parent.is_remote)
|
||||
self.assertNotEqual(server.parent, client.context)
|
||||
self.assertEqual(server.parent.span_id, client.context.span_id)
|
||||
@ -408,7 +409,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
|
||||
self.assertEqual(len(spans), 3)
|
||||
auditor, server, client = spans
|
||||
|
||||
self.assertEqual(server.name, "FinishedHandler.get")
|
||||
self.assertEqual(server.name, "GET /on_finish")
|
||||
self.assertTrue(server.parent.is_remote)
|
||||
self.assertNotEqual(server.parent, client.context)
|
||||
self.assertEqual(server.parent.span_id, client.context.span_id)
|
||||
@ -535,7 +536,7 @@ class TestTornadoInstrumentationWithXHeaders(TornadoTest):
|
||||
self.assertEqual(response.code, 201)
|
||||
spans = self.get_finished_spans()
|
||||
self.assertSpanHasAttributes(
|
||||
spans.by_name("MainHandler.get"),
|
||||
spans.by_name("GET /"),
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
SpanAttributes.HTTP_SCHEME: "http",
|
||||
@ -609,7 +610,7 @@ class TestTornadoUninstrument(TornadoTest):
|
||||
self.assertEqual(len(spans), 3)
|
||||
manual, server, client = self.sorted_spans(spans)
|
||||
self.assertEqual(manual.name, "manual")
|
||||
self.assertEqual(server.name, "MainHandler.get")
|
||||
self.assertEqual(server.name, "GET /")
|
||||
self.assertEqual(client.name, "GET")
|
||||
self.memory_exporter.clear()
|
||||
|
||||
|
@ -207,7 +207,7 @@ def _instrument(
|
||||
|
||||
method = request.get_method().upper()
|
||||
|
||||
span_name = f"HTTP {method}".strip()
|
||||
span_name = method.strip()
|
||||
|
||||
url = remove_url_credentials(url)
|
||||
|
||||
|
@ -124,7 +124,7 @@ class RequestsIntegrationTestBase(abc.ABC):
|
||||
span = self.assert_span()
|
||||
|
||||
self.assertIs(span.kind, trace.SpanKind.CLIENT)
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
self.assertEqual(
|
||||
span.attributes,
|
||||
@ -209,7 +209,7 @@ class RequestsIntegrationTestBase(abc.ABC):
|
||||
span = self.assert_span()
|
||||
|
||||
self.assertIs(span.kind, trace.SpanKind.CLIENT)
|
||||
self.assertEqual(span.name, "HTTP GET")
|
||||
self.assertEqual(span.name, "GET")
|
||||
|
||||
self.assertEqual(
|
||||
span.attributes,
|
||||
|
@ -225,7 +225,7 @@ def _instrument(
|
||||
headers = _prepare_headers(kwargs)
|
||||
body = _get_url_open_arg("body", args, kwargs)
|
||||
|
||||
span_name = f"HTTP {method.strip()}"
|
||||
span_name = method.strip()
|
||||
span_attributes = {
|
||||
SpanAttributes.HTTP_METHOD: method,
|
||||
SpanAttributes.HTTP_URL: url,
|
||||
|
@ -87,7 +87,7 @@ class TestURLLib3Instrumentor(TestBase):
|
||||
|
||||
span = self.assert_span()
|
||||
self.assertIs(trace.SpanKind.CLIENT, span.kind)
|
||||
self.assertEqual("HTTP GET", span.name)
|
||||
self.assertEqual("GET", span.name)
|
||||
|
||||
attributes = {
|
||||
SpanAttributes.HTTP_METHOD: "GET",
|
||||
|
@ -77,7 +77,7 @@ class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase):
|
||||
|
||||
span = self.assert_span()
|
||||
self.assertIs(trace.SpanKind.CLIENT, span.kind)
|
||||
self.assertEqual("HTTP GET", span.name)
|
||||
self.assertEqual("GET", span.name)
|
||||
|
||||
attributes = {
|
||||
"http.status_code": 200,
|
||||
|
@ -440,8 +440,21 @@ def add_response_attributes(
|
||||
|
||||
|
||||
def get_default_span_name(environ):
|
||||
"""Default implementation for name_callback, returns HTTP {METHOD_NAME}."""
|
||||
return f"HTTP {environ.get('REQUEST_METHOD', '')}".strip()
|
||||
"""
|
||||
Default span name is the HTTP method and URL path, or just the method.
|
||||
https://github.com/open-telemetry/opentelemetry-specification/pull/3165
|
||||
https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#name
|
||||
|
||||
Args:
|
||||
environ: The WSGI environ object.
|
||||
Returns:
|
||||
The span name.
|
||||
"""
|
||||
method = environ.get("REQUEST_METHOD", "").strip()
|
||||
path = environ.get("PATH_INFO", "").strip()
|
||||
if method and path:
|
||||
return f"{method} {path}"
|
||||
return method
|
||||
|
||||
|
||||
class OpenTelemetryMiddleware:
|
||||
|
@ -128,7 +128,7 @@ class TestWsgiApplication(WsgiTestBase):
|
||||
self,
|
||||
response,
|
||||
error=None,
|
||||
span_name="HTTP GET",
|
||||
span_name="GET /",
|
||||
http_method="GET",
|
||||
span_attributes=None,
|
||||
response_headers=None,
|
||||
@ -284,12 +284,13 @@ class TestWsgiApplication(WsgiTestBase):
|
||||
)
|
||||
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
|
||||
|
||||
def test_default_span_name_missing_request_method(self):
|
||||
"""Test that default span_names with missing request method."""
|
||||
self.environ.pop("REQUEST_METHOD")
|
||||
def test_default_span_name_missing_path_info(self):
|
||||
"""Test that default span_names with missing path info."""
|
||||
self.environ.pop("PATH_INFO")
|
||||
method = self.environ.get("REQUEST_METHOD", "").strip()
|
||||
app = otel_wsgi.OpenTelemetryMiddleware(simple_wsgi)
|
||||
response = app(self.environ, self.start_response)
|
||||
self.validate_response(response, span_name="HTTP", http_method=None)
|
||||
self.validate_response(response, span_name=method)
|
||||
|
||||
|
||||
class TestWsgiAttributes(unittest.TestCase):
|
||||
@ -455,7 +456,7 @@ class TestWsgiMiddlewareWithTracerProvider(WsgiTestBase):
|
||||
response,
|
||||
exporter,
|
||||
error=None,
|
||||
span_name="HTTP GET",
|
||||
span_name="GET /",
|
||||
http_method="GET",
|
||||
):
|
||||
while True:
|
||||
|
@ -95,7 +95,7 @@ def _start_internal_or_server_span(
|
||||
|
||||
Args:
|
||||
tracer : tracer in use by given instrumentation library
|
||||
name (string): name of the span
|
||||
span_name (string): name of the span
|
||||
start_time : start time of the span
|
||||
context_carrier : object which contains values that are
|
||||
used to construct a Context. This object
|
||||
|
Reference in New Issue
Block a user