mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 17:34:38 +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))
|
([#3133](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3133))
|
||||||
- `opentelemetry-instrumentation-falcon` add support version to v4
|
- `opentelemetry-instrumentation-falcon` add support version to v4
|
||||||
([#3086](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3086))
|
([#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
|
- `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))
|
([#3148](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3148))
|
||||||
- add support to Python 3.13
|
- add support to Python 3.13
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | experimental
|
| [opentelemetry-instrumentation-dbapi](./opentelemetry-instrumentation-dbapi) | dbapi | No | experimental
|
||||||
| [opentelemetry-instrumentation-django](./opentelemetry-instrumentation-django) | django >= 1.10 | Yes | 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-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-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | Yes | migration
|
||||||
| [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0 | 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
|
| [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
|
import opentelemetry.instrumentation.wsgi as otel_wsgi
|
||||||
from opentelemetry import context, trace
|
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.package import _instruments
|
||||||
from opentelemetry.instrumentation.falcon.version import __version__
|
from opentelemetry.instrumentation.falcon.version import __version__
|
||||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||||
@ -203,18 +211,22 @@ from opentelemetry.instrumentation.propagators import (
|
|||||||
from opentelemetry.instrumentation.utils import (
|
from opentelemetry.instrumentation.utils import (
|
||||||
_start_internal_or_server_span,
|
_start_internal_or_server_span,
|
||||||
extract_attributes_from_object,
|
extract_attributes_from_object,
|
||||||
http_status_to_status_code,
|
|
||||||
)
|
)
|
||||||
from opentelemetry.metrics import get_meter
|
from opentelemetry.metrics import get_meter
|
||||||
|
from opentelemetry.semconv.attributes.http_attributes import (
|
||||||
|
HTTP_ROUTE,
|
||||||
|
)
|
||||||
from opentelemetry.semconv.metrics import MetricInstruments
|
from opentelemetry.semconv.metrics import MetricInstruments
|
||||||
from opentelemetry.semconv.trace import SpanAttributes
|
from opentelemetry.semconv.metrics.http_metrics import (
|
||||||
from opentelemetry.trace.status import Status, StatusCode
|
HTTP_SERVER_REQUEST_DURATION,
|
||||||
|
)
|
||||||
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
|
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
|
||||||
|
|
||||||
_logger = getLogger(__name__)
|
_logger = getLogger(__name__)
|
||||||
|
|
||||||
_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
|
_ENVIRON_STARTTIME_KEY = "opentelemetry-falcon.starttime_key"
|
||||||
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
|
_ENVIRON_SPAN_KEY = "opentelemetry-falcon.span_key"
|
||||||
|
_ENVIRON_REQ_ATTRS = "opentelemetry-falcon.req_attrs"
|
||||||
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
|
_ENVIRON_ACTIVATION_KEY = "opentelemetry-falcon.activation_key"
|
||||||
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
|
_ENVIRON_TOKEN = "opentelemetry-falcon.token"
|
||||||
_ENVIRON_EXC = "opentelemetry-falcon.exc"
|
_ENVIRON_EXC = "opentelemetry-falcon.exc"
|
||||||
@ -243,6 +255,10 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
otel_opts = kwargs.pop("_otel_opts", {})
|
otel_opts = kwargs.pop("_otel_opts", {})
|
||||||
|
|
||||||
|
self._sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
|
||||||
|
_OpenTelemetryStabilitySignalType.HTTP,
|
||||||
|
)
|
||||||
|
|
||||||
# inject trace middleware
|
# inject trace middleware
|
||||||
self._middlewares_list = kwargs.pop("middleware", [])
|
self._middlewares_list = kwargs.pop("middleware", [])
|
||||||
if self._middlewares_list is None:
|
if self._middlewares_list is None:
|
||||||
@ -257,19 +273,30 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||||||
__name__,
|
__name__,
|
||||||
__version__,
|
__version__,
|
||||||
tracer_provider,
|
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(
|
self._otel_meter = get_meter(
|
||||||
__name__,
|
__name__,
|
||||||
__version__,
|
__version__,
|
||||||
meter_provider,
|
meter_provider,
|
||||||
schema_url="https://opentelemetry.io/schemas/1.11.0",
|
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
|
||||||
)
|
|
||||||
self.duration_histogram = self._otel_meter.create_histogram(
|
|
||||||
name=MetricInstruments.HTTP_SERVER_DURATION,
|
|
||||||
unit="ms",
|
|
||||||
description="Measures the duration of inbound HTTP requests.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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(
|
self.active_requests_counter = self._otel_meter.create_up_down_counter(
|
||||||
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
|
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
|
||||||
unit="requests",
|
unit="requests",
|
||||||
@ -283,6 +310,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||||||
),
|
),
|
||||||
otel_opts.pop("request_hook", None),
|
otel_opts.pop("request_hook", None),
|
||||||
otel_opts.pop("response_hook", None),
|
otel_opts.pop("response_hook", None),
|
||||||
|
self._sem_conv_opt_in_mode,
|
||||||
)
|
)
|
||||||
self._middlewares_list.insert(0, trace_middleware)
|
self._middlewares_list.insert(0, trace_middleware)
|
||||||
kwargs["middleware"] = self._middlewares_list
|
kwargs["middleware"] = self._middlewares_list
|
||||||
@ -343,11 +371,14 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||||||
context_carrier=env,
|
context_carrier=env,
|
||||||
context_getter=otel_wsgi.wsgi_getter,
|
context_getter=otel_wsgi.wsgi_getter,
|
||||||
)
|
)
|
||||||
attributes = otel_wsgi.collect_request_attributes(env)
|
attributes = otel_wsgi.collect_request_attributes(
|
||||||
active_requests_count_attrs = (
|
env, self._sem_conv_opt_in_mode
|
||||||
otel_wsgi._parse_active_request_count_attrs(attributes)
|
)
|
||||||
|
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)
|
self.active_requests_counter.add(1, active_requests_count_attrs)
|
||||||
|
|
||||||
if span.is_recording():
|
if span.is_recording():
|
||||||
@ -364,6 +395,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||||||
activation.__enter__()
|
activation.__enter__()
|
||||||
env[_ENVIRON_SPAN_KEY] = span
|
env[_ENVIRON_SPAN_KEY] = span
|
||||||
env[_ENVIRON_ACTIVATION_KEY] = activation
|
env[_ENVIRON_ACTIVATION_KEY] = activation
|
||||||
|
env[_ENVIRON_REQ_ATTRS] = attributes
|
||||||
exception = None
|
exception = None
|
||||||
|
|
||||||
def _start_response(status, response_headers, *args, **kwargs):
|
def _start_response(status, response_headers, *args, **kwargs):
|
||||||
@ -379,12 +411,22 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||||||
exception = exc
|
exception = exc
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
if span.is_recording():
|
duration_s = default_timer() - start
|
||||||
duration_attrs[SpanAttributes.HTTP_STATUS_CODE] = (
|
if self.duration_histogram_old:
|
||||||
span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
|
duration_attrs = otel_wsgi._parse_duration_attrs(
|
||||||
|
attributes, _StabilityMode.DEFAULT
|
||||||
)
|
)
|
||||||
duration = max(round((default_timer() - start) * 1000), 0)
|
self.duration_histogram_old.record(
|
||||||
self.duration_histogram.record(duration, duration_attrs)
|
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)
|
self.active_requests_counter.add(-1, active_requests_count_attrs)
|
||||||
if exception is None:
|
if exception is None:
|
||||||
activation.__exit__(None, None, None)
|
activation.__exit__(None, None, None)
|
||||||
@ -407,11 +449,13 @@ class _TraceMiddleware:
|
|||||||
traced_request_attrs=None,
|
traced_request_attrs=None,
|
||||||
request_hook=None,
|
request_hook=None,
|
||||||
response_hook=None,
|
response_hook=None,
|
||||||
|
sem_conv_opt_in_mode: _StabilityMode = _StabilityMode.DEFAULT,
|
||||||
):
|
):
|
||||||
self.tracer = tracer
|
self.tracer = tracer
|
||||||
self._traced_request_attrs = traced_request_attrs
|
self._traced_request_attrs = traced_request_attrs
|
||||||
self._request_hook = request_hook
|
self._request_hook = request_hook
|
||||||
self._response_hook = response_hook
|
self._response_hook = response_hook
|
||||||
|
self._sem_conv_opt_in_mode = sem_conv_opt_in_mode
|
||||||
|
|
||||||
def process_request(self, req, resp):
|
def process_request(self, req, resp):
|
||||||
span = req.env.get(_ENVIRON_SPAN_KEY)
|
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
|
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201,R0912
|
||||||
span = req.env.get(_ENVIRON_SPAN_KEY)
|
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
|
return
|
||||||
|
|
||||||
status = resp.status
|
status = resp.status
|
||||||
reason = None
|
|
||||||
if resource is None:
|
if resource is None:
|
||||||
status = "404"
|
status = falcon.HTTP_404
|
||||||
reason = "NotFound"
|
|
||||||
else:
|
else:
|
||||||
|
exc_type, exc = None, None
|
||||||
if _ENVIRON_EXC in req.env:
|
if _ENVIRON_EXC in req.env:
|
||||||
exc = req.env[_ENVIRON_EXC]
|
exc = req.env[_ENVIRON_EXC]
|
||||||
exc_type = type(exc)
|
exc_type = type(exc)
|
||||||
else:
|
|
||||||
exc_type, exc = None, None
|
|
||||||
if exc_type and not req_succeeded:
|
if exc_type and not req_succeeded:
|
||||||
if "HTTPNotFound" in exc_type.__name__:
|
if "HTTPNotFound" in exc_type.__name__:
|
||||||
status = "404"
|
status = falcon.HTTP_404
|
||||||
reason = "NotFound"
|
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:
|
else:
|
||||||
status = "500"
|
status = falcon.HTTP_500
|
||||||
reason = f"{exc_type.__name__}: {exc}"
|
|
||||||
|
|
||||||
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:
|
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:
|
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
|
||||||
# Check if low-cardinality route is available as per semantic-conventions
|
# Check if low-cardinality route is available as per semantic-conventions
|
||||||
if req.uri_template:
|
if req.uri_template:
|
||||||
span.update_name(f"{req.method} {req.uri_template}")
|
span.update_name(f"{req.method} {req.uri_template}")
|
||||||
|
span.set_attribute(HTTP_ROUTE, req.uri_template)
|
||||||
else:
|
else:
|
||||||
span.update_name(f"{req.method}")
|
span.update_name(f"{req.method}")
|
||||||
|
|
||||||
|
@ -16,3 +16,5 @@
|
|||||||
_instruments = ("falcon >= 1.4.1, < 5.0.0",)
|
_instruments = ("falcon >= 1.4.1, < 5.0.0",)
|
||||||
|
|
||||||
_supports_metrics = True
|
_supports_metrics = True
|
||||||
|
|
||||||
|
_semconv_status = "migration"
|
||||||
|
@ -22,7 +22,11 @@ from packaging import version as package_version
|
|||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.instrumentation._semconv import (
|
from opentelemetry.instrumentation._semconv import (
|
||||||
|
OTEL_SEMCONV_STABILITY_OPT_IN,
|
||||||
|
_OpenTelemetrySemanticConventionStability,
|
||||||
|
_server_active_requests_count_attrs_new,
|
||||||
_server_active_requests_count_attrs_old,
|
_server_active_requests_count_attrs_old,
|
||||||
|
_server_duration_attrs_new,
|
||||||
_server_duration_attrs_old,
|
_server_duration_attrs_old,
|
||||||
)
|
)
|
||||||
from opentelemetry.instrumentation.falcon import FalconInstrumentor
|
from opentelemetry.instrumentation.falcon import FalconInstrumentor
|
||||||
@ -36,6 +40,24 @@ from opentelemetry.sdk.metrics.export import (
|
|||||||
NumberDataPoint,
|
NumberDataPoint,
|
||||||
)
|
)
|
||||||
from opentelemetry.sdk.resources import Resource
|
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.semconv.trace import SpanAttributes
|
||||||
from opentelemetry.test.test_base import TestBase
|
from opentelemetry.test.test_base import TestBase
|
||||||
from opentelemetry.test.wsgitestutil import WsgiTestBase
|
from opentelemetry.test.wsgitestutil import WsgiTestBase
|
||||||
@ -52,10 +74,33 @@ _expected_metric_names = [
|
|||||||
"http.server.active_requests",
|
"http.server.active_requests",
|
||||||
"http.server.duration",
|
"http.server.duration",
|
||||||
]
|
]
|
||||||
|
|
||||||
_recommended_attrs = {
|
_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.active_requests": _server_active_requests_count_attrs_old,
|
||||||
"http.server.duration": _server_duration_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)
|
_parsed_falcon_version = package_version.parse(_falcon_version)
|
||||||
|
|
||||||
@ -63,13 +108,26 @@ _parsed_falcon_version = package_version.parse(_falcon_version)
|
|||||||
class TestFalconBase(TestBase):
|
class TestFalconBase(TestBase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
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(
|
self.env_patch = patch.dict(
|
||||||
"os.environ",
|
"os.environ",
|
||||||
{
|
{
|
||||||
"OTEL_PYTHON_FALCON_EXCLUDED_URLS": "ping",
|
"OTEL_PYTHON_FALCON_EXCLUDED_URLS": "ping",
|
||||||
"OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS": "query_string",
|
"OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS": "query_string",
|
||||||
|
OTEL_SEMCONV_STABILITY_OPT_IN: sem_conv_mode,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_OpenTelemetrySemanticConventionStability._initialized = False
|
||||||
self.env_patch.start()
|
self.env_patch.start()
|
||||||
|
|
||||||
FalconInstrumentor().instrument(
|
FalconInstrumentor().instrument(
|
||||||
@ -95,26 +153,63 @@ class TestFalconBase(TestBase):
|
|||||||
self.env_patch.stop()
|
self.env_patch.stop()
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-many-public-methods
|
||||||
class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
self._test_method("GET")
|
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):
|
def test_post(self):
|
||||||
self._test_method("POST")
|
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):
|
def test_patch(self):
|
||||||
self._test_method("PATCH")
|
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):
|
def test_put(self):
|
||||||
self._test_method("PUT")
|
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):
|
def test_delete(self):
|
||||||
self._test_method("DELETE")
|
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):
|
def test_head(self):
|
||||||
self._test_method("HEAD")
|
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")
|
self.client().simulate_request(method=method, path="/hello")
|
||||||
spans = self.memory_exporter.get_finished_spans()
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
self.assertEqual(len(spans), 1)
|
self.assertEqual(len(spans), 1)
|
||||||
@ -125,23 +220,42 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
|||||||
span.status.description,
|
span.status.description,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
self.assertSpanHasAttributes(
|
|
||||||
span,
|
expected_attributes = {}
|
||||||
{
|
expected_attributes_old = {
|
||||||
SpanAttributes.HTTP_METHOD: method,
|
SpanAttributes.HTTP_METHOD: method,
|
||||||
SpanAttributes.HTTP_SERVER_NAME: "falconframework.org",
|
SpanAttributes.HTTP_SERVER_NAME: "falconframework.org",
|
||||||
SpanAttributes.HTTP_SCHEME: "http",
|
SpanAttributes.HTTP_SCHEME: "http",
|
||||||
SpanAttributes.NET_HOST_PORT: 80,
|
SpanAttributes.NET_HOST_PORT: 80,
|
||||||
SpanAttributes.HTTP_HOST: "falconframework.org",
|
SpanAttributes.HTTP_HOST: "falconframework.org",
|
||||||
SpanAttributes.HTTP_TARGET: "/"
|
SpanAttributes.HTTP_TARGET: "/"
|
||||||
if self._has_fixed_http_target
|
if self._has_fixed_http_target
|
||||||
else "/hello",
|
else "/hello",
|
||||||
SpanAttributes.NET_PEER_PORT: 65133,
|
SpanAttributes.NET_PEER_PORT: 65133,
|
||||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||||
"falcon.resource": "HelloWorldResource",
|
"falcon.resource": "HelloWorldResource",
|
||||||
SpanAttributes.HTTP_STATUS_CODE: 201,
|
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 always set by default to 127.0.0.1
|
||||||
# In falcon>=3, NET_PEER_IP is not set to anything by default
|
# 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
|
# 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.assertEqual(span.name, "GET /error")
|
||||||
self.assertFalse(span.status.is_ok)
|
self.assertFalse(span.status.is_ok)
|
||||||
self.assertEqual(span.status.status_code, StatusCode.ERROR)
|
self.assertEqual(span.status.status_code, StatusCode.ERROR)
|
||||||
self.assertEqual(
|
|
||||||
span.status.description,
|
_parsed_falcon_version = package_version.parse(_falcon_version)
|
||||||
"NameError: name 'non_existent_var' is not defined",
|
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(
|
self.assertSpanHasAttributes(
|
||||||
span,
|
span,
|
||||||
{
|
{
|
||||||
@ -211,6 +331,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
|||||||
SpanAttributes.NET_PEER_PORT: 65133,
|
SpanAttributes.NET_PEER_PORT: 65133,
|
||||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||||
SpanAttributes.HTTP_STATUS_CODE: 500,
|
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
|
# 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"
|
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):
|
def test_url_template(self):
|
||||||
self.client().simulate_get("/user/123")
|
self.client().simulate_get("/user/123")
|
||||||
spans = self.memory_exporter.get_finished_spans()
|
spans = self.memory_exporter.get_finished_spans()
|
||||||
@ -247,6 +409,7 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
|||||||
SpanAttributes.HTTP_FLAVOR: "1.1",
|
SpanAttributes.HTTP_FLAVOR: "1.1",
|
||||||
"falcon.resource": "UserResource",
|
"falcon.resource": "UserResource",
|
||||||
SpanAttributes.HTTP_STATUS_CODE: 200,
|
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_attribute.called)
|
||||||
self.assertFalse(mock_span.set_status.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):
|
def test_uninstrument_after_instrument(self):
|
||||||
self.client().simulate_get(path="/hello")
|
self.client().simulate_get(path="/hello")
|
||||||
spans = self.memory_exporter.get_finished_spans()
|
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)
|
self.assertTrue(number_data_point_seen and histogram_data_point_seen)
|
||||||
|
|
||||||
def test_falcon_metric_values(self):
|
def test_falcon_metric_values_new_semconv(self):
|
||||||
expected_duration_attributes = {
|
number_data_point_seen = False
|
||||||
"http.method": "GET",
|
histogram_data_point_seen = False
|
||||||
"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",
|
|
||||||
}
|
|
||||||
start = default_timer()
|
start = default_timer()
|
||||||
self.client().simulate_get("/hello/756")
|
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()
|
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||||
for resource_metric in metrics_list.resource_metrics:
|
for resource_metric in metrics_list.resource_metrics:
|
||||||
for scope_metric in resource_metric.scope_metrics:
|
for scope_metric in resource_metric.scope_metrics:
|
||||||
for metric in scope_metric.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):
|
if isinstance(point, HistogramDataPoint):
|
||||||
self.assertDictEqual(
|
|
||||||
expected_duration_attributes,
|
|
||||||
dict(point.attributes),
|
|
||||||
)
|
|
||||||
self.assertEqual(point.count, 1)
|
self.assertEqual(point.count, 1)
|
||||||
|
histogram_data_point_seen = True
|
||||||
self.assertAlmostEqual(
|
self.assertAlmostEqual(
|
||||||
duration, point.sum, delta=10
|
duration, point.sum, delta=10
|
||||||
)
|
)
|
||||||
if isinstance(point, NumberDataPoint):
|
if isinstance(point, NumberDataPoint):
|
||||||
self.assertDictEqual(
|
|
||||||
expected_requests_count_attributes,
|
|
||||||
dict(point.attributes),
|
|
||||||
)
|
|
||||||
self.assertEqual(point.value, 0)
|
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):
|
def test_metric_uninstrument(self):
|
||||||
self.client().simulate_request(method="POST", path="/hello/756")
|
self.client().simulate_request(method="POST", path="/hello/756")
|
||||||
|
Reference in New Issue
Block a user