Compare commits

..

6 Commits

Author SHA1 Message Date
leo e336122cfa 💬(frontend) clarify video recording wording
Video recording from transcription panel did not explicitly
mention video, leading to confusion from some users. Make
wording more explicit.
2026-09-09 20:03:36 +02:00
lebaudantoine 172dc70649 (backend) allow configuring trace sampling
Add a configuration knob for the trace sampling rate, so we can
enable tracing on middleware and cache spans when debugging slow
requests in production.

Sampling is set to 0 by default, so tracing stays fully off unless
explicitly enabled.
2026-09-09 20:02:57 +02:00
lebaudantoine e0ab7f191f 📈(frontend) include LiveKit SIDs in the connection analytics event
Attach the LiveKit SIDs (room and participant) to the connection
analytics event.

Makes it easier to debug problematic sessions and to correlate a
room session with the corresponding LiveKit logs.
2026-09-09 13:38:44 +02:00
lebaudantoine 455b315dbb 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422
Around 0.76% of incoming LiveKit webhooks were being flagged as
unprocessable and returned a 422, even though LiveKit was sending
legitimate data — just with event types we do not handle. This
inflated error metrics and made real webhook issues harder to spot.

Return a 200 for these webhooks instead. When a new, unhandled
event type shows up, log a warning so we can decide whether it is
worth adding explicit handling.
2026-09-09 12:09:13 +02:00
lebaudantoine 3089b03062 🔇(backend) silence noisy request summary info logs
The request summary info logs were spamming the log stream, making
around 46% of the total volume, without carrying any exploitable
information.

Silence them so the remaining logs are easier to explore and cheaper
to store; roughly halves the overall log volume.
2026-09-09 11:17:49 +02:00
lebaudantoine 60febb3b57 🔇(backend) silence expected 401 warnings on /me
On a busy morning, `/me` alone produced 72k warning logs — 97% of
all warnings. They all come from anonymous requests to `/me`
without credentials, which is normal: `/me` is how the app
determines the current auth status.

These warnings carry no diagnostic value on this endpoint, so
silence them there to cut down on log volume.
2026-09-09 11:17:49 +02:00
9 changed files with 80 additions and 10 deletions
+6
View File
@@ -13,6 +13,12 @@ and this project adheres to
- 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422 - 🐛(backend) acknowledge unknown LiveKit webhook events instead of 422
- 🔒️(backend) enforce display name setting on rename API - 🔒️(backend) enforce display name setting on rename API
### Changed
- 📈(frontend) include LiveKit SIDs in the connection analytics event
- 🔇(backend) silence expected 401 warnings on /me
- 🔇(backend) silence noisy request summary info logs
## [1.31.0] - 2026-09-08 ## [1.31.0] - 2026-09-08
### Added ### Added
+25
View File
@@ -0,0 +1,25 @@
"""Logging filters for the core application."""
import logging
from django.conf import settings
class SilenceExpected401(logging.Filter):
"""Drop the expected 401 from anonymous hits on the /me endpoint.
The frontend probes `/users/me/` to check authentication; a 401 for
anonymous users is normal, not a warning worth logging.
"""
def filter(self, record):
"""Return False for a 401 on a silenced path, True otherwise."""
if getattr(record, "status_code", None) != 401:
return True
request = getattr(record, "request", None)
path = getattr(request, "path", None)
if not path:
return True
return path not in settings.LOGGING_SILENCED_401_PATHS
+30 -1
View File
@@ -469,6 +469,9 @@ class Base(Configuration):
# Sentry # Sentry
SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN") SENTRY_DSN = values.Value(None, environ_name="SENTRY_DSN")
SENTRY_TRACES_SAMPLE_RATE = values.FloatValue(
0.0, environ_name="SENTRY_TRACES_SAMPLE_RATE", environ_prefix=None
)
# Easy thumbnails # Easy thumbnails
THUMBNAIL_EXTENSION = "webp" THUMBNAIL_EXTENSION = "webp"
@@ -1110,6 +1113,12 @@ class Base(Configuration):
environ_prefix=None, environ_prefix=None,
) )
LOGGING_SILENCED_401_PATHS = values.ListValue(
default=["/api/v1.0/users/me/"],
environ_name="LOGGING_SILENCED_401_PATHS",
environ_prefix=None,
)
# Logging # Logging
# We want to make it easy to log to console but by default we log production # We want to make it easy to log to console but by default we log production
# to Sentry and don't want to log to console. # to Sentry and don't want to log to console.
@@ -1122,10 +1131,16 @@ class Base(Configuration):
"style": "{", "style": "{",
}, },
}, },
"filters": {
"silence_expected_401": {
"()": "core.logging_filters.SilenceExpected401",
},
},
"handlers": { "handlers": {
"console": { "console": {
"class": "logging.StreamHandler", "class": "logging.StreamHandler",
"formatter": "simple", "formatter": "simple",
"filters": ["silence_expected_401"],
}, },
}, },
# Override root logger to send it to console # Override root logger to send it to console
@@ -1136,6 +1151,13 @@ class Base(Configuration):
), ),
}, },
"loggers": { "loggers": {
"request.summary": {
"level": values.Value(
"WARNING",
environ_name="LOGGING_LEVEL_REQUEST_SUMMARY",
environ_prefix="",
)
},
"core": { "core": {
"handlers": ["console"], "handlers": ["console"],
"level": values.Value( "level": values.Value(
@@ -1211,7 +1233,14 @@ class Base(Configuration):
dsn=cls.SENTRY_DSN, dsn=cls.SENTRY_DSN,
environment=cls.__name__.lower(), # build, test, development, production environment=cls.__name__.lower(), # build, test, development, production
release=get_release(), release=get_release(),
integrations=[DjangoIntegration()], traces_sample_rate=cls.SENTRY_TRACES_SAMPLE_RATE,
integrations=[
DjangoIntegration(
transaction_style="url",
middleware_spans=True,
cache_spans=True,
)
],
) )
sentry_sdk.set_tag("application", "backend") sentry_sdk.set_tag("application", "backend")
@@ -71,20 +71,30 @@ export const ConnectionObserver = () => {
useEffect(() => { useEffect(() => {
if (!isAnalyticsEnabled) return if (!isAnalyticsEnabled) return
const handleConnection = () => { const handleConnection = async () => {
// Preserve original connection timestamp across reconnections to measure // Preserve original connection timestamp across reconnections to measure
// total session duration from first connect to final disconnect. // total session duration from first connect to final disconnect.
if (connectionStartTimeRef.current != null) return if (connectionStartTimeRef.current != null) return
connectionStartTimeRef.current = Date.now() connectionStartTimeRef.current = Date.now()
void captureMediaEvent('connection-event', {}) const participantSid = room.localParticipant.sid
const roomSid = await room.getSid().catch(() => undefined)
void captureMediaEvent('connection-event', {
livekit_room_sid: roomSid,
livekit_participant_sid: participantSid,
})
} }
const handleReconnect = () => { const handleReconnect = () => {
captureEvent('reconnect-event') captureEvent('reconnect-event')
} }
const handleReconnected = () => { const handleReconnected = async () => {
captureEvent('reconnected-event') const participantSid = room.localParticipant.sid
const roomSid = await room.getSid().catch(() => undefined)
captureEvent('reconnected-event', {
livekit_room_sid: roomSid,
livekit_participant_sid: participantSid,
})
} }
const handleSignalingConnect = () => { const handleSignalingConnect = () => {
+1 -1
View File
@@ -480,7 +480,7 @@
"destination": "Ein neues Dokument wird erstellt auf", "destination": "Ein neues Dokument wird erstellt auf",
"destinationUnknown": "Ein neues Dokument wird erstellt", "destinationUnknown": "Ein neues Dokument wird erstellt",
"language": "Meeting-Sprache:", "language": "Meeting-Sprache:",
"recording": "Auch eine Aufzeichnung starten" "recording": "Auch eine Videoaufzeichnung starten"
}, },
"button": { "button": {
"start": "Meeting-Transkription starten", "start": "Meeting-Transkription starten",
+1 -1
View File
@@ -480,7 +480,7 @@
"destination": "A new document will be created on", "destination": "A new document will be created on",
"destinationUnknown": "A new document will be created", "destinationUnknown": "A new document will be created",
"language": "Meeting language:", "language": "Meeting language:",
"recording": "Also start a recording" "recording": "Also start a video recording"
}, },
"button": { "button": {
"start": "Start transcribing the meeting", "start": "Start transcribing the meeting",
+1 -1
View File
@@ -479,7 +479,7 @@
"destination": "Se creará un nuevo documento en", "destination": "Se creará un nuevo documento en",
"destinationUnknown": "Se creará un nuevo documento", "destinationUnknown": "Se creará un nuevo documento",
"language": "Idioma de la reunión:", "language": "Idioma de la reunión:",
"recording": "Iniciar también una grabación" "recording": "Iniciar también una grabación de vídeo"
}, },
"button": { "button": {
"start": "Empezar a transcribir la reunión", "start": "Empezar a transcribir la reunión",
+1 -1
View File
@@ -480,7 +480,7 @@
"destination": "Un nouveau document sera créé sur", "destination": "Un nouveau document sera créé sur",
"destinationUnknown": "Un nouveau document sera créé", "destinationUnknown": "Un nouveau document sera créé",
"language": "Langue de la réunion :", "language": "Langue de la réunion :",
"recording": "Démarrer aussi un enregistrement" "recording": "Démarrer aussi un enregistrement vidéo"
}, },
"button": { "button": {
"start": "Commencer à transcrire la réunion", "start": "Commencer à transcrire la réunion",
+1 -1
View File
@@ -480,7 +480,7 @@
"destination": "Er wordt een nieuw document aangemaakt op", "destination": "Er wordt een nieuw document aangemaakt op",
"destinationUnknown": "Een nieuw document wordt aangemaakt", "destinationUnknown": "Een nieuw document wordt aangemaakt",
"language": "Vergadertalen:", "language": "Vergadertalen:",
"recording": "Start ook een opname" "recording": "Start ook een video-opname"
}, },
"button": { "button": {
"start": "Begin met het transcriberen van de vergadering", "start": "Begin met het transcriberen van de vergadering",