Component Analyticsยค
Use the Component.on_render_after()
hook to track component analytics, such as capturing errors for a service like Sentry or other monitoring.
Error tracking componentsยค
You can create a wrapper component that uses the Component.on_render_after()
hook to inspect the error
object. If an error occurred during the rendering of its children, you can capture and send it to your monitoring service.
{% component "sentry_error_tracker" %}
{% component "api_widget" simulate_error=True / %}
{% endcomponent %}
The same hook can be used to track both successes and failures, allowing you to monitor the reliability of a component.
{% component "success_rate_tracker" %}
{% component "api_widget" simulate_error=False / %}
{% endcomponent %}
Error tracking extensionยค
Capturing analytics through components is simple, but limiting:
- You can't access metadata nor state of the component that errored
- Component will capture at most one error
- You must remember to call the component that captures the analytics
Instead, you can define the analytics logic as an extension. This will allow us to capture all errors, without polluting the UI.
To do that, we can use the on_component_rendered()
hook to capture all errors.
from django_components.extension import ComponentExtension, OnComponentRenderedContext
class ErrorTrackingExtension(ComponentExtension):
name = "sentry_error_tracker"
def on_component_rendered(self, ctx: OnComponentRenderedContext):
if ctx.error:
print(f"SENTRY: Captured error in component {ctx.component.name}: {ctx.error}")
Don't forget to register the extension:
Definitionยค
from typing import Dict, List, NamedTuple
from django_components import Component, register, types
DESCRIPTION = "Track component errors or success rates to send them to Sentry or other services."
# A mock analytics service
analytics_events: List[Dict] = []
error_rate = {
"error": 0,
"success": 0,
}
@register("api_widget")
class ApiWidget(Component):
class Kwargs(NamedTuple):
simulate_error: bool = False
def get_template_data(self, args, kwargs: Kwargs, slots, context):
if kwargs.simulate_error:
raise ConnectionError("API call failed")
return {"data": "Mock API response data"}
template: types.django_html = """
<div class="p-4 border rounded-lg bg-gray-50">
<h4 class="font-bold text-gray-800">API Widget</h4>
<p class="text-gray-600">Data: {{ data }}</p>
</div>
"""
@register("sentry_error_tracker")
class SentryErrorTracker(Component):
def on_render_after(self, context, template, result, error):
if error:
event = {
"type": "error",
"component": self.registered_name,
"error": error,
}
analytics_events.append(event)
print(f"SENTRY: Captured error in component {self.registered_name}: {error}")
template: types.django_html = """
{% load component_tags %}
{% slot "default" / %}
"""
@register("success_rate_tracker")
class SuccessRateTracker(Component):
def on_render_after(self, context, template, result, error):
# Track error
if error:
error_rate["error"] += 1
# Track success
else:
error_rate["success"] += 1
template: types.django_html = """
{% load component_tags %}
{% slot "default" / %}
"""
Exampleยค
To see the component in action, you can set up a view and a URL pattern as shown below.
views.py
ยค
from django.http import HttpRequest, HttpResponse
from django_components import Component, register, types
from .component import analytics_events, error_rate
class AnalyticsPage(Component):
class Media:
js = ("https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,container-queries",)
template: types.django_html = """
{% load component_tags %}
<html>
<head>
<title>Analytics Example</title>
</head>
<body class="bg-gray-100 p-8">
<div class="max-w-4xl mx-auto bg-white p-6 rounded-lg shadow-md">
<h1 class="text-2xl font-bold mb-4">
Component Analytics
</h1>
<p class="text-gray-600 mb-6">
Track component errors or success rates to send them
to Sentry or other services.
</p>
{# NOTE: Intentionally hidden so we focus on the events tracking #}
<div style="display: none;">
{% component "template_with_errors" / %}
</div>
{% component "captured_events" / %}
</div>
</body>
</html>
"""
class View:
def get(self, request: HttpRequest) -> HttpResponse:
# Clear events on each page load
analytics_events.clear()
error_rate["error"] = 0
error_rate["success"] = 0
return AnalyticsPage.render_to_response(request=request)
@register("template_with_errors")
class TemplateWithErrors(Component):
template: types.django_html = """
<div class="mb-8">
<h2 class="text-xl font-semibold mb-2">
Sentry Error Tracking
</h2>
<p class="text-sm text-gray-500 mb-2">
This component only logs events when an error occurs.
</p>
{% component "error_fallback" %}
{% component "sentry_error_tracker" %}
{% component "api_widget" simulate_error=True / %}
{% endcomponent %}
{% endcomponent %}
{% component "sentry_error_tracker" %}
{% component "api_widget" simulate_error=False / %}
{% endcomponent %}
</div>
<div>
<h2 class="text-xl font-semibold mb-2">
Success Rate Analytics
</h2>
<p class="text-sm text-gray-500 mb-2">
This component logs both successful and failed renders.
</p>
{% component "error_fallback" %}
{% component "success_rate_tracker" %}
{% component "api_widget" simulate_error=True / %}
{% endcomponent %}
{% endcomponent %}
{% component "success_rate_tracker" %}
{% component "api_widget" simulate_error=False / %}
{% endcomponent %}
</div>
"""
# NOTE: Since this runs after `template_with_errors`,
# the `analytics_events` will be populated.
@register("captured_events")
class CapturedEvents(Component):
def get_template_data(self, args, kwargs, slots, context):
return {"events": analytics_events, "error_rate": error_rate}
template: types.django_html = """
<div class="mt-8 p-4 border rounded-lg bg-gray-50">
<h3 class="text-lg font-semibold mb-2">
Captured Analytics Events
</h3>
<pre class="text-sm text-gray-700 whitespace-pre-wrap">
{% for event in events %}
{{ event }}
{% endfor %}
</pre>
</div>
<div class="mt-8 p-4 border rounded-lg bg-gray-50">
<h3 class="text-lg font-semibold mb-2">
Error Rate
</h3>
<pre class="text-sm text-gray-700 whitespace-pre-wrap">
{{ error_rate }}
</pre>
<p class="text-sm text-gray-500">
{{ error_rate.error }} errors out of {{ error_rate.success }} calls.
</p>
</div>
"""