mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-30 05:32:30 +08:00
Implement new HTTP semantic convention opt-in for Falcon (#2790)
This commit is contained in:
@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
([#3133](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3133))
|
||||
- `opentelemetry-instrumentation-falcon` add support version to v4
|
||||
([#3086](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3086))
|
||||
- `opentelemetry-instrumentation-falcon` Implement new HTTP semantic convention opt-in for Falcon
|
||||
([#2790](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2790))
|
||||
- `opentelemetry-instrumentation-wsgi` always record span status code to have it available in metrics
|
||||
([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148))
|
||||
- add support to Python 3.13
|
||||
|
@ -20,7 +20,7 @@
|
||||
| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | experimental
|
||||
| [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | Yes | experimental
|
||||
| [opentelemetry-instrumentation-elasticsearch](./opentelemetry-instrumentation-elasticsearch) | elasticsearch >= 6.0 | No | experimental
|
||||
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | experimental
|
||||
| [opentelemetry-instrumentation-falcon](./opentelemetry-instrumentation-falcon) | falcon >= 1.4.1, < 5.0.0 | Yes | migration
|
||||
| [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | migration
|
||||
| [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | Yes | migration
|
||||
| [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio >= 1.42.0 | No | experimental
|
||||
|
@ -193,6 +193,14 @@ from packaging import version as package_version
|
||||
|
||||
import opentelemetry.instrumentation.wsgi as otel_wsgi
|
||||
from opentelemetry import context, trace
|
||||
from opentelemetry.instrumentation._semconv import (
|
||||
_get_schema_url,
|
||||
_OpenTelemetrySemanticConventionStability,
|
||||
_OpenTelemetryStabilitySignalType,
|
||||
_report_new,
|
||||
_report_old,
|
||||
_StabilityMode,
|
||||
)
|
||||
from opentelemetry.instrumentation.falcon.package import _instruments
|
||||
from opentelemetry.instrumentation.falcon.version import __version__
|
||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||
@ -203,18 +211,22 @@ from opentelemetry.instrumentation.propagators import (
|
||||
from opentelemetry.instrumentation.utils import (
|
||||
_start_internal_or_server_span,
|
||||
extract_attributes_from_object,
|
||||
http_status_to_status_code,
|
||||
)
|
||||
from opentelemetry.metrics import get_meter
|
||||
from opentelemetry.semconv.attributes.http_attributes import (
|
||||
HTTP_ROUTE,
|
||||
)
|
||||
from opentelemetry.semconv.metrics import MetricInstruments
|
||||
from opentelemetry.semconv.trace import SpanAttributes
|
||||
from opentelemetry.trace.status import Status, StatusCode
|
||||
from opentelemetry.semconv.metrics.http_metrics import (
|
||||
HTTP_SERVER_REQUEST_DURATION,
|
||||
)
|
||||
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
|
||||
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
|
||||
_ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
|
||||
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
|
||||
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
|
||||
_ENVIRON_EXC = "opentelemetry-falcon.exc"
|
||||
@ -243,6 +255,10 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
||||
def __init__(self, *args, **kwargs):
|
||||
otel_opts = kwargs.pop("_otel_opts", {})
|
||||
|
||||
self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||
_OpenTelemetryStabilitySignalType.HTTP,
|
||||
)
|
||||
|
||||
# inject trace middleware
|
||||
self._middlewares_list = kwargs.pop("middleware", [])
|
||||
if self._middlewares_list is None:
|
||||
@ -257,19 +273,30 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
||||
__name__,
|
||||
__version__,
|
||||
tracer_provider,
|
||||
schema_url="https://opentelemetry.io/schemas/1.11.0",
|
||||
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
|
||||
)
|
||||
self._otel_meter = get_meter(
|
||||
__name__,
|
||||
__version__,
|
||||
meter_provider,
|
||||
schema_url="https://opentelemetry.io/schemas/1.11.0",
|
||||
)
|
||||
self.duration_histogram = self._otel_meter.create_histogram(
|
||||
name=MetricInstruments.HTTP_SERVER_DURATION,
|
||||
unit="ms",
|
||||
description="Measures the duration of inbound HTTP requests.",
|
||||
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
|
||||
)
|
||||
|
||||
self.duration_histogram_old = None
|
||||
if _report_old(self._sem_conv_opt_in_mode):
|
||||
self.duration_histogram_old = self._otel_meter.create_histogram(
|
||||
name=MetricInstruments.HTTP_SERVER_DURATION,
|
||||
unit="ms",
|
||||
description="Measures the duration of inbound HTTP requests.",
|
||||
)
|
||||
self.duration_histogram_new = None
|
||||
if _report_new(self._sem_conv_opt_in_mode):
|
||||
self.duration_histogram_new = self._otel_meter.create_histogram(
|
||||
name=HTTP_SERVER_REQUEST_DURATION,
|
||||
description="Duration of HTTP server requests.",
|
||||
unit="s",
|
||||
)
|
||||
|
||||
self.active_requests_counter = self._otel_meter.create_up_down_counter(
|
||||
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
|
||||
unit="requests",
|
||||
@ -283,6 +310,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
||||
),
|
||||
otel_opts.pop("request_hook", None),
|
||||
otel_opts.pop("response_hook", None),
|
||||
self._sem_conv_opt_in_mode,
|
||||
)
|
||||
self._middlewares_list.insert(0, trace_middleware)
|
||||
kwargs["middleware"] = self._middlewares_list
|
||||
@ -343,11 +371,14 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
||||
context_carrier=env,
|
||||
context_getter=otel_wsgi.wsgi_getter,
|
||||
)
|
||||
attributes = otel_wsgi.collect_request_attributes(env)
|
||||
active_requests_count_attrs = (
|
||||
otel_wsgi._parse_active_request_count_attrs(attributes)
|
||||
attributes = otel_wsgi.collect_request_attributes(
|
||||
env, self._sem_conv_opt_in_mode
|
||||
)
|
||||
active_requests_count_attrs = (
|
||||
otel_wsgi._parse_active_request_count_attrs(
|
||||
attributes, self._sem_conv_opt_in_mode
|
||||
)
|
||||
)
|
||||
duration_attrs = otel_wsgi._parse_duration_attrs(attributes)
|
||||
self.active_requests_counter.add(1, active_requests_count_attrs)
|
||||
|
||||
if span.is_recording():
|
||||
@ -364,6 +395,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
||||
activation.__enter__()
|
||||
env[_ENVIRON_SPAN_KEY] = span
|
||||
env[_ENVIRON_ACTIVATION_KEY] = activation
|
||||
env[_ENVIRON_REQ_ATTRS] = attributes
|
||||
exception = None
|
||||
|
||||
def _start_response(status, response_headers, *args, **kwargs):
|
||||
@ -379,12 +411,22 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
||||
exception = exc
|
||||
raise
|
||||
finally:
|
||||
if span.is_recording():
|
||||
duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = (
|
||||
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
|
||||
duration_s = default_timer() - start
|
||||
if self.duration_histogram_old:
|
||||
duration_attrs = otel_wsgi._parse_duration_attrs(
|
||||
attributes, _StabilityMode.DEFAULT
|
||||
)
|
||||
duration = max(round((default_timer() - start) * 1000), 0)
|
||||
self.duration_histogram.record(duration, duration_attrs)
|
||||
self.duration_histogram_old.record(
|
||||
max(round(duration_s * 1000), 0), duration_attrs
|
||||
)
|
||||
if self.duration_histogram_new:
|
||||
duration_attrs = otel_wsgi._parse_duration_attrs(
|
||||
attributes, _StabilityMode.HTTP
|
||||
)
|
||||
self.duration_histogram_new.record(
|
||||
max(duration_s, 0), duration_attrs
|
||||
)
|
||||
|
||||
self.active_requests_counter.add(-1, active_requests_count_attrs)
|
||||
if exception is None:
|
||||
activation.__exit__(None, None, None)
|
||||
@ -407,11 +449,13 @@ class _TraceMiddleware:
|
||||
traced_request_attrs=None,
|
||||
request_hook=None,
|
||||
response_hook=None,
|
||||
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
|
||||
):
|
||||
self.tracer = tracer
|
||||
self._traced_request_attrs = traced_request_attrs
|
||||
self._request_hook = request_hook
|
||||
self._response_hook = response_hook
|
||||
self._sem_conv_opt_in_mode = sem_conv_opt_in_mode
|
||||
|
||||
def process_request(self, req, resp):
|
||||
span = req.env.get(_ENVIRON_SPAN_KEY)
|
||||
@ -437,58 +481,60 @@ class _TraceMiddleware:
|
||||
|
||||
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201,R0912
|
||||
span = req.env.get(_ENVIRON_SPAN_KEY)
|
||||
req_attrs = req.env.get(_ENVIRON_REQ_ATTRS)
|
||||
|
||||
if not span or not span.is_recording():
|
||||
if not span:
|
||||
return
|
||||
|
||||
status = resp.status
|
||||
reason = None
|
||||
if resource is None:
|
||||
status = "404"
|
||||
reason = "NotFound"
|
||||
status = falcon.HTTP_404
|
||||
else:
|
||||
exc_type, exc = None, None
|
||||
if _ENVIRON_EXC in req.env:
|
||||
exc = req.env[_ENVIRON_EXC]
|
||||
exc_type = type(exc)
|
||||
else:
|
||||
exc_type, exc = None, None
|
||||
|
||||
if exc_type and not req_succeeded:
|
||||
if "HTTPNotFound" in exc_type.__name__:
|
||||
status = "404"
|
||||
reason = "NotFound"
|
||||
status = falcon.HTTP_404
|
||||
elif isinstance(exc, (falcon.HTTPError, falcon.HTTPStatus)):
|
||||
try:
|
||||
if _falcon_version > 2:
|
||||
status = falcon.code_to_http_status(exc.status)
|
||||
else:
|
||||
status = exc.status
|
||||
except ValueError:
|
||||
status = falcon.HTTP_500
|
||||
else:
|
||||
status = "500"
|
||||
reason = f"{exc_type.__name__}: {exc}"
|
||||
status = falcon.HTTP_500
|
||||
|
||||
status = status.split(" ")[0]
|
||||
# Falcon 1 does not support response headers. So
|
||||
# send an empty dict.
|
||||
response_headers = {}
|
||||
if _falcon_version > 1:
|
||||
response_headers = resp.headers
|
||||
|
||||
otel_wsgi.add_response_attributes(
|
||||
span,
|
||||
status,
|
||||
response_headers,
|
||||
req_attrs,
|
||||
self._sem_conv_opt_in_mode,
|
||||
)
|
||||
|
||||
if (
|
||||
_report_new(self._sem_conv_opt_in_mode)
|
||||
and req.uri_template
|
||||
and req_attrs is not None
|
||||
):
|
||||
req_attrs[HTTP_ROUTE] = req.uri_template
|
||||
try:
|
||||
status_code = int(status)
|
||||
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
|
||||
otel_status_code = http_status_to_status_code(
|
||||
status_code, server_span=True
|
||||
)
|
||||
|
||||
# set the description only when the status code is ERROR
|
||||
if otel_status_code is not StatusCode.ERROR:
|
||||
reason = None
|
||||
|
||||
span.set_status(
|
||||
Status(
|
||||
status_code=otel_status_code,
|
||||
description=reason,
|
||||
)
|
||||
)
|
||||
|
||||
# Falcon 1 does not support response headers. So
|
||||
# send an empty dict.
|
||||
response_headers = {}
|
||||
if _falcon_version > 1:
|
||||
response_headers = resp.headers
|
||||
|
||||
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
|
||||
# Check if low-cardinality route is available as per semantic-conventions
|
||||
if req.uri_template:
|
||||
span.update_name(f"{req.method} {req.uri_template}")
|
||||
span.set_attribute(HTTP_ROUTE, req.uri_template)
|
||||
else:
|
||||
span.update_name(f"{req.method}")
|
||||
|
||||
|
@ -16,3 +16,5 @@
|
||||
_instruments = ("falcon >= 1.4.1, < 5.0.0",)
|
||||
|
||||
_supports_metrics = True
|
||||
|
||||
_semconv_status = "migration"
|
||||
|
@ -22,7 +22,11 @@ from packaging import version as package_version
|
||||
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.instrumentation._semconv import (
|
||||
OTEL_SEMCONV_STABILITY_OPT_IN,
|
||||
_OpenTelemetrySemanticConventionStability,
|
||||
_server_active_requests_count_attrs_new,
|
||||
_server_active_requests_count_attrs_old,
|
||||
_server_duration_attrs_new,
|
||||
_server_duration_attrs_old,
|
||||
)
|
||||
from opentelemetry.instrumentation.falcon import FalconInstrumentor
|
||||
@ -36,6 +40,24 @@ from opentelemetry.sdk.metrics.export import (
|
||||
NumberDataPoint,
|
||||
)
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.semconv.attributes.client_attributes import (
|
||||
CLIENT_PORT,
|
||||
)
|
||||
from opentelemetry.semconv.attributes.http_attributes import (
|
||||
HTTP_REQUEST_METHOD,
|
||||
HTTP_RESPONSE_STATUS_CODE,
|
||||
)
|
||||
from opentelemetry.semconv.attributes.network_attributes import (
|
||||
NETWORK_PROTOCOL_VERSION,
|
||||
)
|
||||
from opentelemetry.semconv.attributes.server_attributes import (
|
||||
SERVER_ADDRESS,
|
||||
SERVER_PORT,
|
||||
)
|
||||
from opentelemetry.semconv.attributes.url_attributes import (
|
||||
URL_PATH,
|
||||
URL_SCHEME,
|
||||
)
|
||||
from opentelemetry.semconv.trace import SpanAttributes
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
from opentelemetry.test.wsgitestutil import WsgiTestBase
|
||||
@ -52,10 +74,33 @@ _expected_metric_names = [
|
||||
"http.server.active_requests",
|
||||
"http.server.duration",
|
||||
]
|
||||
|
||||
_recommended_attrs = {
|
||||
"http.server.active_requests": _server_active_requests_count_attrs_new
|
||||
+ _server_active_requests_count_attrs_old,
|
||||
"http.server.duration": _server_duration_attrs_new
|
||||
+ _server_duration_attrs_old,
|
||||
}
|
||||
|
||||
_recommended_metrics_attrs_old = {
|
||||
"http.server.active_requests": _server_active_requests_count_attrs_old,
|
||||
"http.server.duration": _server_duration_attrs_old,
|
||||
}
|
||||
_recommended_metrics_attrs_new = {
|
||||
"http.server.active_requests": _server_active_requests_count_attrs_new,
|
||||
"http.server.request.duration": _server_duration_attrs_new,
|
||||
}
|
||||
_server_active_requests_count_attrs_both = (
|
||||
_server_active_requests_count_attrs_old
|
||||
)
|
||||
_server_active_requests_count_attrs_both.extend(
|
||||
_server_active_requests_count_attrs_new
|
||||
)
|
||||
_recommended_metrics_attrs_both = {
|
||||
"http.server.active_requests": _server_active_requests_count_attrs_both,
|
||||
"http.server.duration": _server_duration_attrs_old,
|
||||
"http.server.request.duration": _server_duration_attrs_new,
|
||||
}
|
||||
|
||||
_parsed_falcon_version = package_version.parse(_falcon_version)
|
||||
|
||||
@ -63,13 +108,26 @@ _parsed_falcon_version = package_version.parse(_falcon_version)
|
||||
class TestFalconBase(TestBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
test_name = ""
|
||||
if hasattr(self, "_testMethodName"):
|
||||
test_name = self._testMethodName
|
||||
sem_conv_mode = "default"
|
||||
if "new_semconv" in test_name:
|
||||
sem_conv_mode = "http"
|
||||
elif "both_semconv" in test_name:
|
||||
sem_conv_mode = "http/dup"
|
||||
|
||||
self.env_patch = patch.dict(
|
||||
"os.environ",
|
||||
{
|
||||
"OTEL_PYTHON_FALCON_EXCLUDED_URLS": "ping",
|
||||
"OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS": "query_string",
|
||||
OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode,
|
||||
},
|
||||
)
|
||||
|
||||
_OpenTelemetrySemanticConventionStability._initialized = False
|
||||
self.env_patch.start()
|
||||
|
||||
FalconInstrumentor().instrument(
|
||||
@ -95,26 +153,63 @@ class TestFalconBase(TestBase):
|
||||
self.env_patch.stop()
|
||||
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
def test_get(self):
|
||||
self._test_method("GET")
|
||||
|
||||
def test_get_new_semconv(self):
|
||||
self._test_method("GET", old_semconv=False, new_semconv=True)
|
||||
|
||||
def test_get_both_semconv(self):
|
||||
self._test_method("GET", old_semconv=True, new_semconv=True)
|
||||
|
||||
def test_post(self):
|
||||
self._test_method("POST")
|
||||
|
||||
def test_post_new_semconv(self):
|
||||
self._test_method("POST", old_semconv=False, new_semconv=True)
|
||||
|
||||
def test_post_both_semconv(self):
|
||||
self._test_method("POST", old_semconv=True, new_semconv=True)
|
||||
|
||||
def test_patch(self):
|
||||
self._test_method("PATCH")
|
||||
|
||||
def test_patch_new_semconv(self):
|
||||
self._test_method("PATCH", old_semconv=False, new_semconv=True)
|
||||
|
||||
def test_patch_both_semconv(self):
|
||||
self._test_method("PATCH", old_semconv=True, new_semconv=True)
|
||||
|
||||
def test_put(self):
|
||||
self._test_method("PUT")
|
||||
|
||||
def test_put_new_semconv(self):
|
||||
self._test_method("PUT", old_semconv=False, new_semconv=True)
|
||||
|
||||
def test_put_both_semconv(self):
|
||||
self._test_method("PUT", old_semconv=True, new_semconv=True)
|
||||
|
||||
def test_delete(self):
|
||||
self._test_method("DELETE")
|
||||
|
||||
def test_delete_new_semconv(self):
|
||||
self._test_method("DELETE", old_semconv=False, new_semconv=True)
|
||||
|
||||
def test_delete_both_semconv(self):
|
||||
self._test_method("DELETE", old_semconv=True, new_semconv=True)
|
||||
|
||||
def test_head(self):
|
||||
self._test_method("HEAD")
|
||||
|
||||
def _test_method(self, method):
|
||||
def test_head_new_semconv(self):
|
||||
self._test_method("HEAD", old_semconv=False, new_semconv=True)
|
||||
|
||||
def test_head_both_semconv(self):
|
||||
self._test_method("HEAD", old_semconv=True, new_semconv=True)
|
||||
|
||||
def _test_method(self, method, old_semconv=True, new_semconv=False):
|
||||
self.client().simulate_request(method=method, path="/hello")
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 1)
|
||||
@ -125,23 +220,42 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
span.status.description,
|
||||
None,
|
||||
)
|
||||
self.assertSpanHasAttributes(
|
||||
span,
|
||||
{
|
||||
SpanAttributes.HTTP_METHOD: method,
|
||||
SpanAttributes.HTTP_SERVER_NAME: "falconframework.org",
|
||||
SpanAttributes.HTTP_SCHEME: "http",
|
||||
SpanAttributes.NET_HOST_PORT: 80,
|
||||
SpanAttributes.HTTP_HOST: "falconframework.org",
|
||||
SpanAttributes.HTTP_TARGET: "/"
|
||||
if self._has_fixed_http_target
|
||||
else "/hello",
|
||||
SpanAttributes.NET_PEER_PORT: 65133,
|
||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||
"falcon.resource": "HelloWorldResource",
|
||||
SpanAttributes.HTTP_STATUS_CODE: 201,
|
||||
},
|
||||
)
|
||||
|
||||
expected_attributes = {}
|
||||
expected_attributes_old = {
|
||||
SpanAttributes.HTTP_METHOD: method,
|
||||
SpanAttributes.HTTP_SERVER_NAME: "falconframework.org",
|
||||
SpanAttributes.HTTP_SCHEME: "http",
|
||||
SpanAttributes.NET_HOST_PORT: 80,
|
||||
SpanAttributes.HTTP_HOST: "falconframework.org",
|
||||
SpanAttributes.HTTP_TARGET: "/"
|
||||
if self._has_fixed_http_target
|
||||
else "/hello",
|
||||
SpanAttributes.NET_PEER_PORT: 65133,
|
||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||
"falcon.resource": "HelloWorldResource",
|
||||
SpanAttributes.HTTP_STATUS_CODE: 201,
|
||||
SpanAttributes.HTTP_ROUTE: "/hello",
|
||||
}
|
||||
expected_attributes_new = {
|
||||
HTTP_REQUEST_METHOD: method,
|
||||
SERVER_ADDRESS: "falconframework.org",
|
||||
URL_SCHEME: "http",
|
||||
SERVER_PORT: 80,
|
||||
URL_PATH: "/" if self._has_fixed_http_target else "/hello",
|
||||
CLIENT_PORT: 65133,
|
||||
NETWORK_PROTOCOL_VERSION: "1.1",
|
||||
"falcon.resource": "HelloWorldResource",
|
||||
HTTP_RESPONSE_STATUS_CODE: 201,
|
||||
SpanAttributes.HTTP_ROUTE: "/hello",
|
||||
}
|
||||
|
||||
if old_semconv:
|
||||
expected_attributes.update(expected_attributes_old)
|
||||
if new_semconv:
|
||||
expected_attributes.update(expected_attributes_new)
|
||||
|
||||
self.assertSpanHasAttributes(span, expected_attributes)
|
||||
# In falcon<3, NET_PEER_IP is always set by default to 127.0.0.1
|
||||
# In falcon>=3, NET_PEER_IP is not set to anything by default
|
||||
# https://github.com/falconry/falcon/blob/5233d0abed977d9dab78ebadf305f5abe2eef07c/falcon/testing/helpers.py#L1168-L1172 # noqa
|
||||
@ -193,10 +307,16 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
self.assertEqual(span.name, "GET /error")
|
||||
self.assertFalse(span.status.is_ok)
|
||||
self.assertEqual(span.status.status_code, StatusCode.ERROR)
|
||||
self.assertEqual(
|
||||
span.status.description,
|
||||
"NameError: name 'non_existent_var' is not defined",
|
||||
)
|
||||
|
||||
_parsed_falcon_version = package_version.parse(_falcon_version)
|
||||
if _parsed_falcon_version < package_version.parse("3.0.0"):
|
||||
self.assertEqual(
|
||||
span.status.description,
|
||||
"NameError: name 'non_existent_var' is not defined",
|
||||
)
|
||||
else:
|
||||
self.assertEqual(span.status.description, None)
|
||||
|
||||
self.assertSpanHasAttributes(
|
||||
span,
|
||||
{
|
||||
@ -211,6 +331,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
SpanAttributes.NET_PEER_PORT: 65133,
|
||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||
SpanAttributes.HTTP_STATUS_CODE: 500,
|
||||
SpanAttributes.HTTP_ROUTE: "/error",
|
||||
},
|
||||
)
|
||||
# In falcon<3, NET_PEER_IP is always set by default to 127.0.0.1
|
||||
@ -221,6 +342,47 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
span.attributes[SpanAttributes.NET_PEER_IP], "127.0.0.1"
|
||||
)
|
||||
|
||||
def test_url_template_new_semconv(self):
|
||||
self.client().simulate_get("/user/123")
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||
|
||||
self.assertEqual(len(spans), 1)
|
||||
self.assertTrue(len(metrics_list.resource_metrics) != 0)
|
||||
span = spans[0]
|
||||
self.assertEqual(span.name, "GET /user/{user_id}")
|
||||
self.assertEqual(span.status.status_code, StatusCode.UNSET)
|
||||
self.assertEqual(
|
||||
span.status.description,
|
||||
None,
|
||||
)
|
||||
self.assertSpanHasAttributes(
|
||||
span,
|
||||
{
|
||||
HTTP_REQUEST_METHOD: "GET",
|
||||
SERVER_ADDRESS: "falconframework.org",
|
||||
URL_SCHEME: "http",
|
||||
SERVER_PORT: 80,
|
||||
URL_PATH: "/" if self._has_fixed_http_target else "/user/123",
|
||||
CLIENT_PORT: 65133,
|
||||
NETWORK_PROTOCOL_VERSION: "1.1",
|
||||
"falcon.resource": "UserResource",
|
||||
HTTP_RESPONSE_STATUS_CODE: 200,
|
||||
SpanAttributes.HTTP_ROUTE: "/user/{user_id}",
|
||||
},
|
||||
)
|
||||
|
||||
for resource_metric in metrics_list.resource_metrics:
|
||||
for scope_metric in resource_metric.scope_metrics:
|
||||
for metric in scope_metric.metrics:
|
||||
if metric.name == "http.server.request.duration":
|
||||
data_points = list(metric.data.data_points)
|
||||
for point in data_points:
|
||||
self.assertIn(
|
||||
"http.route",
|
||||
point.attributes,
|
||||
)
|
||||
|
||||
def test_url_template(self):
|
||||
self.client().simulate_get("/user/123")
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
@ -247,6 +409,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||
"falcon.resource": "UserResource",
|
||||
SpanAttributes.HTTP_STATUS_CODE: 200,
|
||||
SpanAttributes.HTTP_ROUTE: "/user/{user_id}",
|
||||
},
|
||||
)
|
||||
|
||||
@ -305,6 +468,27 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
self.assertFalse(mock_span.set_attribute.called)
|
||||
self.assertFalse(mock_span.set_status.called)
|
||||
|
||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||
self.assertTrue(len(metrics_list.resource_metrics) != 0)
|
||||
|
||||
for resource_metric in metrics_list.resource_metrics:
|
||||
for scope_metric in resource_metric.scope_metrics:
|
||||
for metric in scope_metric.metrics:
|
||||
data_points = list(metric.data.data_points)
|
||||
self.assertEqual(len(data_points), 1)
|
||||
for point in list(metric.data.data_points):
|
||||
if isinstance(point, HistogramDataPoint):
|
||||
self.assertEqual(point.count, 1)
|
||||
if isinstance(point, NumberDataPoint):
|
||||
self.assertEqual(point.value, 0)
|
||||
for attr in point.attributes:
|
||||
self.assertIn(
|
||||
attr,
|
||||
_recommended_metrics_attrs_old[
|
||||
metric.name
|
||||
],
|
||||
)
|
||||
|
||||
def test_uninstrument_after_instrument(self):
|
||||
self.client().simulate_get(path="/hello")
|
||||
spans = self.memory_exporter.get_finished_spans()
|
||||
@ -345,47 +529,119 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||
)
|
||||
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
|
||||
|
||||
def test_falcon_metric_values(self):
|
||||
expected_duration_attributes = {
|
||||
"http.method": "GET",
|
||||
"http.host": "falconframework.org",
|
||||
"http.scheme": "http",
|
||||
"http.flavor": "1.1",
|
||||
"http.server_name": "falconframework.org",
|
||||
"net.host.port": 80,
|
||||
"net.host.name": "falconframework.org",
|
||||
"http.status_code": 404,
|
||||
}
|
||||
expected_requests_count_attributes = {
|
||||
"http.method": "GET",
|
||||
"http.host": "falconframework.org",
|
||||
"http.scheme": "http",
|
||||
"http.flavor": "1.1",
|
||||
"http.server_name": "falconframework.org",
|
||||
}
|
||||
def test_falcon_metric_values_new_semconv(self):
|
||||
number_data_point_seen = False
|
||||
histogram_data_point_seen = False
|
||||
|
||||
start = default_timer()
|
||||
self.client().simulate_get("/hello/756")
|
||||
duration = max(round((default_timer() - start) * 1000), 0)
|
||||
duration = max(default_timer() - start, 0)
|
||||
|
||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||
for resource_metric in metrics_list.resource_metrics:
|
||||
for scope_metric in resource_metric.scope_metrics:
|
||||
for metric in scope_metric.metrics:
|
||||
for point in list(metric.data.data_points):
|
||||
data_points = list(metric.data.data_points)
|
||||
self.assertEqual(len(data_points), 1)
|
||||
for point in data_points:
|
||||
if isinstance(point, HistogramDataPoint):
|
||||
self.assertDictEqual(
|
||||
expected_duration_attributes,
|
||||
dict(point.attributes),
|
||||
)
|
||||
self.assertEqual(point.count, 1)
|
||||
histogram_data_point_seen = True
|
||||
self.assertAlmostEqual(
|
||||
duration, point.sum, delta=10
|
||||
)
|
||||
if isinstance(point, NumberDataPoint):
|
||||
self.assertDictEqual(
|
||||
expected_requests_count_attributes,
|
||||
dict(point.attributes),
|
||||
)
|
||||
self.assertEqual(point.value, 0)
|
||||
number_data_point_seen = True
|
||||
for attr in point.attributes:
|
||||
self.assertIn(
|
||||
attr,
|
||||
_recommended_metrics_attrs_new[metric.name],
|
||||
)
|
||||
|
||||
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
|
||||
|
||||
def test_falcon_metric_values_both_semconv(self):
|
||||
number_data_point_seen = False
|
||||
histogram_data_point_seen = False
|
||||
|
||||
start = default_timer()
|
||||
self.client().simulate_get("/hello/756")
|
||||
duration_s = default_timer() - start
|
||||
|
||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
for resource_metric in metrics_list.resource_metrics:
|
||||
for scope_metric in resource_metric.scope_metrics:
|
||||
for metric in scope_metric.metrics:
|
||||
if metric.unit == "ms":
|
||||
self.assertEqual(metric.name, "http.server.duration")
|
||||
elif metric.unit == "s":
|
||||
self.assertEqual(
|
||||
metric.name, "http.server.request.duration"
|
||||
)
|
||||
else:
|
||||
self.assertEqual(
|
||||
metric.name, "http.server.active_requests"
|
||||
)
|
||||
data_points = list(metric.data.data_points)
|
||||
self.assertEqual(len(data_points), 1)
|
||||
for point in data_points:
|
||||
if isinstance(point, HistogramDataPoint):
|
||||
self.assertEqual(point.count, 1)
|
||||
if metric.unit == "ms":
|
||||
self.assertAlmostEqual(
|
||||
max(round(duration_s * 1000), 0),
|
||||
point.sum,
|
||||
delta=10,
|
||||
)
|
||||
elif metric.unit == "s":
|
||||
self.assertAlmostEqual(
|
||||
max(duration_s, 0), point.sum, delta=10
|
||||
)
|
||||
histogram_data_point_seen = True
|
||||
if isinstance(point, NumberDataPoint):
|
||||
self.assertEqual(point.value, 0)
|
||||
number_data_point_seen = True
|
||||
for attr in point.attributes:
|
||||
self.assertIn(
|
||||
attr,
|
||||
_recommended_metrics_attrs_both[metric.name],
|
||||
)
|
||||
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
|
||||
|
||||
def test_falcon_metric_values(self):
|
||||
number_data_point_seen = False
|
||||
histogram_data_point_seen = False
|
||||
|
||||
start = default_timer()
|
||||
self.client().simulate_get("/hello/756")
|
||||
duration = max(round((default_timer() - start) * 1000), 0)
|
||||
|
||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||
for resource_metric in metrics_list.resource_metrics:
|
||||
for scope_metric in resource_metric.scope_metrics:
|
||||
for metric in scope_metric.metrics:
|
||||
data_points = list(metric.data.data_points)
|
||||
self.assertEqual(len(data_points), 1)
|
||||
for point in list(metric.data.data_points):
|
||||
if isinstance(point, HistogramDataPoint):
|
||||
self.assertEqual(point.count, 1)
|
||||
histogram_data_point_seen = True
|
||||
self.assertAlmostEqual(
|
||||
duration, point.sum, delta=10
|
||||
)
|
||||
if isinstance(point, NumberDataPoint):
|
||||
self.assertEqual(point.value, 0)
|
||||
number_data_point_seen = True
|
||||
for attr in point.attributes:
|
||||
self.assertIn(
|
||||
attr,
|
||||
_recommended_metrics_attrs_old[metric.name],
|
||||
)
|
||||
|
||||
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
|
||||
|
||||
def test_metric_uninstrument(self):
|
||||
self.client().simulate_request(method="POST", path="/hello/756")
|
||||
|
Reference in New Issue
Block a user