Skip to content

Allow _DjangoMiddleware to have span creation overridden #3652

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- `opentelemetry-instrumentation-django`: Add `_DjangoMiddleware._get_span` for easier customization ([#3652](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3652))

### Fixed

- `opentelemetry-instrumentation`: Fix dependency conflict detection when instrumented packages are not installed by moving check back to before instrumentors are loaded. Add "instruments-any" feature for instrumentations that target multiple packages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
# limitations under the License.

import types
from contextvars import Token
from logging import getLogger
from time import time
from timeit import default_timer
from typing import Callable
from typing import Callable, ContextManager, Union

from django import VERSION as django_version
from django.http import HttpRequest, HttpResponse

from opentelemetry.context import detach
from opentelemetry.context.context import Context
from opentelemetry.instrumentation._semconv import (
_filter_semconv_active_request_count_attr,
_filter_semconv_duration_attrs,
Expand Down Expand Up @@ -207,26 +209,17 @@ def process_request(self, request):

if is_asgi_request:
carrier = request.scope
carrier_getter = asgi_getter
collect_request_attributes = asgi_collect_request_attributes
else:
carrier = request_meta
carrier_getter = wsgi_getter
collect_request_attributes = wsgi_collect_request_attributes

attributes = collect_request_attributes(
carrier,
self._sem_conv_opt_in_mode,
)
span, token = _start_internal_or_server_span(
tracer=self._tracer,
span_name=self._get_span_name(request),
start_time=request_meta.get(
"opentelemetry-instrumentor-django.starttime_key"
),
context_carrier=carrier,
context_getter=carrier_getter,
attributes=attributes,
span, token, activation = self._get_span(
request, attributes, is_asgi_request
)

active_requests_count_attrs = _parse_active_request_count_attrs(
Expand Down Expand Up @@ -282,8 +275,6 @@ def process_request(self, request):
for key, value in attributes.items():
span.set_attribute(key, value)

activation = use_span(span, end_on_exit=True)
activation.__enter__() # pylint: disable=E1101
request_start_time = default_timer()
request.META[self._environ_timer_key] = request_start_time
request.META[self._environ_activation_key] = activation
Expand All @@ -301,6 +292,37 @@ def process_request(self, request):
# would not be called. Log the exception instead.
_logger.exception("Exception raised by request_hook")

def _get_span(
self,
request: Union[HttpRequest, ASGIRequest],
attributes: dict,
is_asgi_request: bool,
) -> tuple[Span, Token[Context], ContextManager]:
request_meta = request.META

if is_asgi_request:
carrier = request.scope
carrier_getter = asgi_getter
else:
carrier = request_meta
carrier_getter = wsgi_getter

span, token = _start_internal_or_server_span(
tracer=self._tracer,
span_name=self._get_span_name(request),
start_time=request_meta.get(
"opentelemetry-instrumentor-django.starttime_key"
),
context_carrier=carrier,
context_getter=carrier_getter,
attributes=attributes,
)

activation = use_span(span, end_on_exit=True)
activation.__enter__() # pylint: disable=E1101

return span, token, activation

# pylint: disable=unused-argument
def process_view(self, request, view_func, *args, **kwargs):
# Process view is executed before the view function, here we get the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import urllib.parse
from contextlib import contextmanager
from contextvars import Token
from importlib import import_module
from re import escape, sub
from typing import Any, Dict, Generator, Sequence
Expand All @@ -30,10 +31,11 @@
_SUPPRESS_HTTP_INSTRUMENTATION_KEY,
_SUPPRESS_INSTRUMENTATION_KEY,
)
from opentelemetry.context.context import Context

# pylint: disable=E0611
from opentelemetry.propagate import extract
from opentelemetry.trace import StatusCode
from opentelemetry.trace import Span, StatusCode
from opentelemetry.trace.propagation.tracecontext import (
TraceContextTextMapPropagator,
)
Expand Down Expand Up @@ -119,7 +121,7 @@ def _start_internal_or_server_span(
context_carrier,
context_getter,
attributes=None,
):
) -> tuple[Span, Token[Context]]:
"""Returns internal or server span along with the token which can be used by caller to reset context


Expand All @@ -136,13 +138,13 @@ def _start_internal_or_server_span(
from carrier.
"""

token = ctx = span_kind = None
if trace.get_current_span() is trace.INVALID_SPAN:
ctx = extract(context_carrier, getter=context_getter)
token = context.attach(ctx)
span_kind = trace.SpanKind.SERVER
else:
ctx = context.get_current()
token = None
span_kind = trace.SpanKind.INTERNAL
span = tracer.start_span(
name=span_name,
Expand Down