Metric instrumentation falcon (#1230)
This commit is contained in:
parent
08db974e26
commit
9a2285a42b
|
|
@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- `opentelemetry-instrumentation-grpc` add supports to filter requests to instrument. ([#1241](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1241))
|
||||
- Flask sqlalchemy psycopg2 integration
|
||||
([#1224](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1224))
|
||||
- Add metric instrumentation in Falcon
|
||||
([#1230](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1230))
|
||||
- Add metric instrumentation in fastapi
|
||||
([#1199](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1199))
|
||||
- Add metric instrumentation in Pyramid
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ API
|
|||
from logging import getLogger
|
||||
from sys import exc_info
|
||||
from time import time_ns
|
||||
from timeit import default_timer
|
||||
from typing import Collection
|
||||
|
||||
import falcon
|
||||
|
|
@ -163,6 +164,7 @@ from opentelemetry.instrumentation.utils import (
|
|||
extract_attributes_from_object,
|
||||
http_status_to_status_code,
|
||||
)
|
||||
from opentelemetry.metrics import get_meter
|
||||
from opentelemetry.semconv.trace import SpanAttributes
|
||||
from opentelemetry.trace.status import Status
|
||||
from opentelemetry.util.http import get_excluded_urls, get_traced_request_attrs
|
||||
|
|
@ -202,12 +204,24 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||
# inject trace middleware
|
||||
self._middlewares_list = kwargs.pop("middleware", [])
|
||||
tracer_provider = otel_opts.pop("tracer_provider", None)
|
||||
meter_provider = otel_opts.pop("meter_provider", None)
|
||||
if not isinstance(self._middlewares_list, (list, tuple)):
|
||||
self._middlewares_list = [self._middlewares_list]
|
||||
|
||||
self._otel_tracer = trace.get_tracer(
|
||||
__name__, __version__, tracer_provider
|
||||
)
|
||||
self._otel_meter = get_meter(__name__, __version__, meter_provider)
|
||||
self.duration_histogram = self._otel_meter.create_histogram(
|
||||
name="http.server.duration",
|
||||
unit="ms",
|
||||
description="measures the duration of the inbound HTTP request",
|
||||
)
|
||||
self.active_requests_counter = self._otel_meter.create_up_down_counter(
|
||||
name="http.server.active_requests",
|
||||
unit="requests",
|
||||
description="measures the number of concurrent HTTP requests that are currently in-flight",
|
||||
)
|
||||
|
||||
trace_middleware = _TraceMiddleware(
|
||||
self._otel_tracer,
|
||||
|
|
@ -261,6 +275,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||
|
||||
def __call__(self, env, start_response):
|
||||
# pylint: disable=E1101
|
||||
# pylint: disable=too-many-locals
|
||||
if self._otel_excluded_urls.url_disabled(env.get("PATH_INFO", "/")):
|
||||
return super().__call__(env, start_response)
|
||||
|
||||
|
|
@ -276,9 +291,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)
|
||||
)
|
||||
duration_attrs = otel_wsgi._parse_duration_attrs(attributes)
|
||||
self.active_requests_counter.add(1, active_requests_count_attrs)
|
||||
|
||||
if span.is_recording():
|
||||
attributes = otel_wsgi.collect_request_attributes(env)
|
||||
for key, value in attributes.items():
|
||||
span.set_attribute(key, value)
|
||||
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
|
||||
|
|
@ -302,6 +322,7 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||
context.detach(token)
|
||||
return response
|
||||
|
||||
start = default_timer()
|
||||
try:
|
||||
return super().__call__(env, _start_response)
|
||||
except Exception as exc:
|
||||
|
|
@ -313,6 +334,13 @@ class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
|
|||
if token is not None:
|
||||
context.detach(token)
|
||||
raise
|
||||
finally:
|
||||
duration_attrs[
|
||||
SpanAttributes.HTTP_STATUS_CODE
|
||||
] = span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
|
||||
duration = max(round((default_timer() - start) * 1000), 0)
|
||||
self.duration_histogram.record(duration, duration_attrs)
|
||||
self.active_requests_counter.add(-1, active_requests_count_attrs)
|
||||
|
||||
|
||||
class _TraceMiddleware:
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
from timeit import default_timer
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -26,6 +27,14 @@ from opentelemetry.instrumentation.propagators import (
|
|||
get_global_response_propagator,
|
||||
set_global_response_propagator,
|
||||
)
|
||||
from opentelemetry.instrumentation.wsgi import (
|
||||
_active_requests_count_attrs,
|
||||
_duration_attrs,
|
||||
)
|
||||
from opentelemetry.sdk.metrics.export import (
|
||||
HistogramDataPoint,
|
||||
NumberDataPoint,
|
||||
)
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.semconv.trace import SpanAttributes
|
||||
from opentelemetry.test.test_base import TestBase
|
||||
|
|
@ -38,6 +47,15 @@ from opentelemetry.util.http import (
|
|||
|
||||
from .app import make_app
|
||||
|
||||
_expected_metric_names = [
|
||||
"http.server.active_requests",
|
||||
"http.server.duration",
|
||||
]
|
||||
_recommended_attrs = {
|
||||
"http.server.active_requests": _active_requests_count_attrs,
|
||||
"http.server.duration": _duration_attrs,
|
||||
}
|
||||
|
||||
|
||||
class TestFalconBase(TestBase):
|
||||
def setUp(self):
|
||||
|
|
@ -254,6 +272,87 @@ class TestFalconInstrumentation(TestFalconBase, WsgiTestBase):
|
|||
spans = self.memory_exporter.get_finished_spans()
|
||||
self.assertEqual(len(spans), 0)
|
||||
|
||||
def test_falcon_metrics(self):
|
||||
self.client().simulate_get("/hello/756")
|
||||
self.client().simulate_get("/hello/756")
|
||||
self.client().simulate_get("/hello/756")
|
||||
metrics_list = self.memory_metrics_reader.get_metrics_data()
|
||||
number_data_point_seen = False
|
||||
histogram_data_point_seen = False
|
||||
self.assertTrue(len(metrics_list.resource_metrics) != 0)
|
||||
for resource_metric in metrics_list.resource_metrics:
|
||||
self.assertTrue(len(resource_metric.scope_metrics) != 0)
|
||||
for scope_metric in resource_metric.scope_metrics:
|
||||
self.assertTrue(len(scope_metric.metrics) != 0)
|
||||
for metric in scope_metric.metrics:
|
||||
self.assertIn(metric.name, _expected_metric_names)
|
||||
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, 3)
|
||||
histogram_data_point_seen = True
|
||||
if isinstance(point, NumberDataPoint):
|
||||
number_data_point_seen = True
|
||||
for attr in point.attributes:
|
||||
self.assertIn(
|
||||
attr, _recommended_attrs[metric.name]
|
||||
)
|
||||
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,
|
||||
"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()
|
||||
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:
|
||||
for point in list(metric.data.data_points):
|
||||
if isinstance(point, HistogramDataPoint):
|
||||
self.assertDictEqual(
|
||||
expected_duration_attributes,
|
||||
dict(point.attributes),
|
||||
)
|
||||
self.assertEqual(point.count, 1)
|
||||
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)
|
||||
|
||||
def test_metric_uninstrument(self):
|
||||
self.client().simulate_request(method="POST", path="/hello/756")
|
||||
FalconInstrumentor().uninstrument()
|
||||
self.client().simulate_request(method="POST", path="/hello/756")
|
||||
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):
|
||||
if isinstance(point, HistogramDataPoint):
|
||||
self.assertEqual(point.count, 1)
|
||||
|
||||
|
||||
class TestFalconInstrumentationWithTracerProvider(TestBase):
|
||||
def setUp(self):
|
||||
|
|
|
|||
Loading…
Reference in New Issue