Issue
I am a newbie to Open Telemetry and trying to see how traces are captured for a Django app. I am assuming that OTel should be able to provide traces of all invoked functions for a particular Django Request. E.g.I have a very basic Django app with a views.py as
def log_console():
print("Print in terminal")
def show_date():
now = datetime.now()
log_console()
return HTMLResponse()
The url is configured to serve show_date
My expectation js that when I configure manage.py
with DjangoInstrumentor().instrument()
In the console I should be able to see reference to the function log_console
as it is invoked when serving the show_date
But cant see any reference to log_console, not sure if this is a valid use case for Tracing in Open Telemetry
Solution
OTel would not instrument all functions by default (like your log_console()
and show_date()
from your view.py
): you need to explicitly instrument the functions you want to capture traces from.
+-------------------+ +---------------+ +-------------------+
| Django Application | ---> | show_date URL | ---> | show_date() View |
+-------------------+ +---------------+ +-------------------+
| |
| OTel Instrumentation V
| +-------------+
+---------------------------------------------------->| log_console |
+-------------+
| OTel Span |
+-------------+
That would involve a Tracer
and a TracerProvider
:
from opentelemetry.sdk import TracerProvider
from opentelemetry.sdk.trace import Tracer
tracer_provider = TracerProvider()
tracer = tracer_provider.get_tracer(__name__)
def log_console():
print("Print in terminal")
with tracer.start_as_current_span("log_console"):
log_console()
That will create a Tracer object and inject it into the log_console()
function. When the log_console()
function is called, a new trace will be created and the log_console()
function will be recorded as a child of that trace. That will make sure the log_console()
function is included in the traces when they are captured using OpenTelemetry.
You need configure the Django app to use OpenTelemetry (see "OpenTelemetry Python / Django Instrumentation"):
from django.core.management import get_commands
from opentelemetry.django.instrumentation import DjangoInstrumentor
def instrument():
DjangoInstrumentor().instrument()
def uninstrument():
DjangoInstrumentor().uninstrument()
get_commands().register('django-instrument', instrument)
get_commands().register('django-uninstrument', uninstrument)
That does add two new commands to the Django management command interface: django-instrument
and django-uninstrument
.
These commands (python manage.py django-instrument
/ python manage.py django-uninstrument
) can be used to enable and disable OpenTelemetry instrumentation for the Django app.
Once the app is instrumented, the traces can be captured using an OpenTelemetry exporter.
For example, this would capture the traces and send them to a Jaeger instance (distributed tracing backend) running on localhost:9411
:
from opentelemetry.exporters import jaeger
exporter = jaeger.JaegerExporter(config=jaeger.Config(agent_address="localhost:9411"))
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(exporter.add_span_processor())
tracer = tracer_provider.get_tracer(__name__)
with tracer.start_span("request"):
# handle the request
That code will create a Jaeger exporter and add it to the TracerProvider
. The TracerProvider
will then use the exporter to capture all of the traces generated by the app. The traces will be sent to the Jaeger instance running on localhost:9411
.
Is it possible to trace without adding the
with tracer.start_as_current_span("log_console")
? (as I have many places and adding these lines in the code will not be an option)
Not really.
For functions that you specifically want to trace and which are not covered by automatic instrumentation, you could create a custom decorator that starts and ends a span. That approach still requires modification of your code, but is more concise than adding tracing code manually to each function.
from functools import wraps
from opentelemetry import trace
def trace_function(func):
@wraps(func)
def wrapper(*args, **kwargs):
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span(func.__name__):
return func(*args, **kwargs)
return wrapper
@trace_function
def your_function():
# your code
Another more advanced and less common approach in Python is using aspect-oriented programming to intercept function calls and add tracing. That is more complex and might involve using additional libraries like aspectlib
. That method allows adding tracing to functions without modifying their code directly but adds complexity to the application.
Finally, as an alternative, for Django applications, much of the tracing can be accomplished via middleware that intercepts HTTP requests. That will not trace every function call, but it will provide traces for each request and its processing lifecycle within Django.
Your Django project's file structure might look something like this:
your_django_project/
│
├── your_django_app/ # Your Django application directory
│ ├── __init__.py # Initialization file for the app
│ ├── views.py # Views for your Django app
│ ├── models.py # Models for your Django app
│ ├── urls.py # URL configurations for your app
│ ├── tracing_middleware.py <===== # Custom middleware for tracing
│ └── ... # Other files (admin.py, tests.py, etc.)
│
├── your_django_project/ # Your Django project directory
│ ├── __init__.py # Initialization file for the project
│ ├── settings.py <===== # Settings/configuration for your project
│ ├── urls.py # Root URL configurations for your project
│ ├── wsgi.py # WSGI configuration for deployment
│ └── ... # Other project-level files
│
├── manage.py # Management script for Django
└── ... # Other files (requirements.txt, etc.)
Create a new file tracing_middleware.py
in your Django app folder:
# your_django_app/tracing_middleware.py
from opentelemetry import trace
class TracingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.tracer = trace.get_tracer(__name__)
def __call__(self, request):
# Start a new span for this request
with self.tracer.start_as_current_span("http_request"):
# Process the request
response = self.get_response(request)
return response
That file contains the TracingMiddleware
class, which uses OpenTelemetry's tracer to start a new span for each HTTP request.
When a request is made, the middleware creates a span, processes the request, and then ends the span when the response is sent.
And in your settings.py
, add the new TracingMiddleware
to the MIDDLEWARE
list:
# your_django_project/settings.py
# Add 'your_django_app.tracing_middleware.TracingMiddleware' to your MIDDLEWARE list
MIDDLEWARE = [
# other middleware classes
'your_django_app.tracing_middleware.TracingMiddleware',
]
By adding TracingMiddleware
to the MIDDLEWARE
list in Django's settings, you make sure this middleware is applied to every request handled by your Django application.
Answered By - VonC
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.