Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP transition for flask and wsgi #2024

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- 'release/*'
pull_request:
env:
CORE_REPO_SHA: 0ef76a5cc39626f783416ca75e43556e2bb2739d
CORE_REPO_SHA: d054dff47d2da663a39b9656d106c3d15f344269

jobs:
build:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,15 @@ def response_hook(span: Span, status: str, response_headers: List):
from opentelemetry.instrumentation.propagators import (
get_global_response_propagator,
)
from opentelemetry.instrumentation.utils import _start_internal_or_server_span
from opentelemetry.instrumentation.utils import (
_start_internal_or_server_span,
_OpenTelemetrySemanticConventionStability,
_OpenTelemetryStabilityMode,
_get_schema_url,
_report_new,
_report_old
)

from opentelemetry.metrics import get_meter
from opentelemetry.semconv.metrics import MetricInstruments
from opentelemetry.semconv.trace import SpanAttributes
Expand Down Expand Up @@ -283,17 +291,20 @@ def _request_ctx_ref() -> weakref.ReferenceType:


def get_default_span_name():
# returns sanitized method since WSGI does not support routing
span_name = otel_wsgi.get_default_span_name(flask.request.environ)
try:
span_name = flask.request.url_rule.rule
return span_name + " " + flask.request.url_rule.rule
except AttributeError:
span_name = otel_wsgi.get_default_span_name(flask.request.environ)
return span_name
return span_name


def _rewrapped_app(
wsgi_app,
active_requests_counter,
duration_histogram,
duration_histogram_old,
duration_histogram_new,
sem_conv_opt_in_mode,
response_hook=None,
excluded_urls=None,
):
Expand All @@ -304,11 +315,10 @@ def _wrapped_app(wrapped_app_environ, start_response):
# we better avoid it.
wrapped_app_environ[_ENVIRON_STARTTIME_KEY] = time_ns()
start = default_timer()
attributes = otel_wsgi.collect_request_attributes(wrapped_app_environ)
attributes = otel_wsgi.collect_request_attributes(wrapped_app_environ, sem_conv_opt_in_mode)
active_requests_count_attrs = (
otel_wsgi._parse_active_request_count_attrs(attributes)
otel_wsgi._filter_active_request_count_attrs(attributes, sem_conv_opt_in_mode)
)
duration_attrs = otel_wsgi._parse_duration_attrs(attributes)
active_requests_counter.add(1, active_requests_count_attrs)

def _start_response(status, response_headers, *args, **kwargs):
Expand All @@ -327,13 +337,8 @@ def _start_response(status, response_headers, *args, **kwargs):

if span:
otel_wsgi.add_response_attributes(
span, status, response_headers
span, status, response_headers, attributes, sem_conv_opt_in_mode
)
status_code = otel_wsgi._parse_status_code(status)
if status_code is not None:
duration_attrs[
SpanAttributes.HTTP_STATUS_CODE
] = status_code
if (
span.is_recording()
and span.kind == trace.SpanKind.SERVER
Expand All @@ -354,8 +359,16 @@ def _start_response(status, response_headers, *args, **kwargs):
return start_response(status, response_headers, *args, **kwargs)

result = wsgi_app(wrapped_app_environ, _start_response)
duration = max(round((default_timer() - start) * 1000), 0)
duration_histogram.record(duration, duration_attrs)

# duration = max(round((default_timer() - start) * 1000), 0)
duration = default_timer() - start
if duration_histogram_old is not None:
duration_attrs_old = otel_wsgi._filter_duration_attrs(attributes, _OpenTelemetryStabilityMode.DEFAULT)
duration_histogram_old.record(max(round(duration * 1000), 0), duration_attrs_old)
if duration_histogram_new is not None:
duration_attrs_new = otel_wsgi._filter_duration_attrs(attributes, _OpenTelemetryStabilityMode.HTTP)
duration_histogram_new.record(duration, duration_attrs_new)

active_requests_counter.add(-1, active_requests_count_attrs)
return result

Expand All @@ -368,6 +381,7 @@ def _wrapped_before_request(
excluded_urls=None,
enable_commenter=True,
commenter_options=None,
sem_conv_opt_in_mode=None,
):
def _before_request():
if excluded_urls and excluded_urls.url_disabled(flask.request.url):
Expand All @@ -376,7 +390,7 @@ def _before_request():
span_name = get_default_span_name()

attributes = otel_wsgi.collect_request_attributes(
flask_request_environ
flask_request_environ, sem_conv_opt_in_mode
)
if flask.request.url_rule:
# For 404 that result from no route found, etc, we
Expand Down Expand Up @@ -487,6 +501,7 @@ class _InstrumentedFlask(flask.Flask):
_enable_commenter = True
_commenter_options = None
_meter_provider = None
_sem_conv_opt_in_mode = None

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -495,29 +510,48 @@ def __init__(self, *args, **kwargs):
self._is_instrumented_by_opentelemetry = True

meter = get_meter(
__name__, __version__, _InstrumentedFlask._meter_provider
)
duration_histogram = meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="measures the duration of the inbound HTTP request",
__name__,
__version__,
_InstrumentedFlask._meter_provider,
schema_url=_get_schema_url(self._sem_conv_opt_in_mode)
)

duration_histogram_old = None
if _report_old(self._sem_conv_opt_in_mode):
duration_histogram_old = meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="measures the duration of the inbound HTTP request",
)
duration_histogram_new = None
if _report_new(self._sem_conv_opt_in_mode):
duration_histogram_new = meter.create_histogram(
name=otel_wsgi._METRIC_INSTRUMENTS_HTTP_SERVER_REQUEST_DURATION,
unit="s",
description="measures the duration of the inbound HTTP request",
)

active_requests_counter = meter.create_up_down_counter(
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
unit="requests",
unit="{request}",
description="measures the number of concurrent HTTP requests that are currently in-flight",
)

self.wsgi_app = _rewrapped_app(
self.wsgi_app,
active_requests_counter,
duration_histogram,
duration_histogram_old,
duration_histogram_new,
self._sem_conv_opt_in_mode,
_InstrumentedFlask._response_hook,
excluded_urls=_InstrumentedFlask._excluded_urls,
)

tracer = trace.get_tracer(
__name__, __version__, _InstrumentedFlask._tracer_provider
__name__,
__version__,
_InstrumentedFlask._tracer_provider,
schema_url=_get_schema_url(self._sem_conv_opt_in_mode),
)

_before_request = _wrapped_before_request(
Expand All @@ -526,6 +560,7 @@ def __init__(self, *args, **kwargs):
excluded_urls=_InstrumentedFlask._excluded_urls,
enable_commenter=_InstrumentedFlask._enable_commenter,
commenter_options=_InstrumentedFlask._commenter_options,
sem_conv_opt_in_mode=self._sem_conv_opt_in_mode
)
self._before_request = _before_request
self.before_request(_before_request)
Expand Down Expand Up @@ -569,6 +604,13 @@ def _instrument(self, **kwargs):
_InstrumentedFlask._commenter_options = commenter_options
meter_provider = kwargs.get("meter_provider")
_InstrumentedFlask._meter_provider = meter_provider

# initialize semantic conventions opt-in if needed
_OpenTelemetrySemanticConventionStability._initialize()

sem_conv_opt_in_mode = kwargs.get("sem_conv_opt_in_mode", _OpenTelemetryStabilityMode.DEFAULT)
_InstrumentedFlask._sem_conv_opt_in_mode = sem_conv_opt_in_mode

flask.Flask = _InstrumentedFlask

def _uninstrument(self, **kwargs):
Expand All @@ -577,6 +619,7 @@ def _uninstrument(self, **kwargs):
@staticmethod
def instrument_app(
app,
sem_conv_opt_in_mode =_OpenTelemetryStabilityMode.DEFAULT,
request_hook=None,
response_hook=None,
tracer_provider=None,
Expand All @@ -594,12 +637,26 @@ def instrument_app(
if excluded_urls is not None
else _excluded_urls_from_env
)
meter = get_meter(__name__, __version__, meter_provider)
duration_histogram = meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="measures the duration of the inbound HTTP request",
meter = get_meter(
__name__,
__version__,
meter_provider,
schema_url=_get_schema_url(sem_conv_opt_in_mode),
)
duration_histogram_old = None
if _report_old(sem_conv_opt_in_mode):
duration_histogram_old = meter.create_histogram(
name=MetricInstruments.HTTP_SERVER_DURATION,
unit="ms",
description="measures the duration of the inbound HTTP request",
)
duration_histogram_new = None
if _report_new(sem_conv_opt_in_mode):
duration_histogram_new = meter.create_histogram(
name=otel_wsgi._METRIC_INSTRUMENTS_HTTP_SERVER_REQUEST_DURATION,
unit="s",
description="measures the duration of the inbound HTTP request",
)
active_requests_counter = meter.create_up_down_counter(
name=MetricInstruments.HTTP_SERVER_ACTIVE_REQUESTS,
unit="requests",
Expand All @@ -610,12 +667,19 @@ def instrument_app(
app.wsgi_app = _rewrapped_app(
app.wsgi_app,
active_requests_counter,
duration_histogram,
duration_histogram_old,
duration_histogram_new,
sem_conv_opt_in_mode,
response_hook,
excluded_urls=excluded_urls,
)

tracer = trace.get_tracer(__name__, __version__, tracer_provider)
tracer = trace.get_tracer(
__name__,
__version__,
tracer_provider,
schema_url=_get_schema_url(sem_conv_opt_in_mode),
)

_before_request = _wrapped_before_request(
request_hook,
Expand All @@ -625,6 +689,7 @@ def instrument_app(
commenter_options=commenter_options
if commenter_options
else {},
sem_conv_opt_in_mode=sem_conv_opt_in_mode
)
app._before_request = _before_request
app.before_request(_before_request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ def test_copycontext(self):
resp = client.get("/copy_context", headers={"x-req": "a-header"})

self.assertEqual(200, resp.status_code)
self.assertEqual("/copy_context", resp.json["span_name"])
self.assertEqual("GET /copy_context", resp.json["span_name"])
self.assertEqual("a-header", resp.json["request_header"])
Loading
Loading