feat: add forum telemetry for comment create update and delete#244
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Datadog “forum.*” telemetry attributes to the Discussion REST API comment create/update/delete flows to support COSMO2-853 observability needs.
Changes:
- Instrument
CommentViewSet.create,partial_update, anddestroywith operation/entity/actor/course/result/error attributes. - Add
_get_comment_trace_contexthelper to fetch comment context for request-level telemetry on update/delete. - Emit
forum.entity_idfromcreate_commentafter the comment is persisted.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
lms/djangoapps/discussion/rest_api/views.py |
Adds telemetry attributes around comment create/update/delete and introduces a helper to fetch comment context. |
lms/djangoapps/discussion/rest_api/api.py |
Records forum.entity_id for newly created comments after serializer save. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| cc_comment, course_id = _get_comment_trace_context(comment_id) | ||
| set_custom_attribute("forum.course_id", str(course_id)) | ||
| if cc_comment.get("parent_id"): | ||
| set_custom_attribute( | ||
| "forum.parent_comment_id", str(cc_comment.get("parent_id")) | ||
| ) | ||
|
|
||
| update_fields = [ | ||
| field | ||
| for field, value in request.data.items() | ||
| if value is not None and field != "voted" | ||
| ] | ||
| if update_fields: | ||
| set_custom_attribute("forum.update_fields", ",".join(update_fields)) | ||
|
|
||
| if request.content_type != MergePatchParser.media_type: | ||
| raise UnsupportedMediaType(request.content_type) | ||
|
|
||
| response = Response(update_comment(request, comment_id, request.data)) |
There was a problem hiding this comment.
partial_update() pre-fetches the comment/thread via _get_comment_trace_context for telemetry, but update_comment() already retrieves the same entities (via _get_comment_and_context) and builds context. This adds redundant forum service calls for every update. Consider reusing the data from update_comment (e.g., have update_comment return cc_comment/context/course_id, or move telemetry attribute setting into api.update_comment where cc_comment is already available).
| def _get_comment_trace_context(comment_id): | ||
| """Retrieve comment context needed for request-level telemetry.""" | ||
| cc_comment = Comment(id=comment_id).retrieve() | ||
| course_id = get_course_id_from_thread_id(cc_comment["thread_id"]) |
There was a problem hiding this comment.
_get_comment_trace_context makes an extra forum service call by deriving course_id via get_course_id_from_thread_id(thread_id), which retrieves the thread. The comment retrieve response already includes course_id (see Comment.accessible_fields), so this can use cc_comment["course_id"] (with a fallback if needed) to avoid the additional network hop and latency per request.
| course_id = get_course_id_from_thread_id(cc_comment["thread_id"]) | |
| course_id = cc_comment.get("course_id") | |
| if not course_id: | |
| course_id = get_course_id_from_thread_id(cc_comment["thread_id"]) |
| cc_comment, course_id = _get_comment_trace_context(comment_id) | ||
| set_custom_attribute("forum.course_id", str(course_id)) | ||
| if cc_comment.get("parent_id"): | ||
| set_custom_attribute( | ||
| "forum.parent_comment_id", str(cc_comment.get("parent_id")) | ||
| ) |
There was a problem hiding this comment.
destroy() now pre-fetches the comment (and its thread/course) for telemetry via _get_comment_trace_context, but delete_comment() already calls _get_comment_and_context() internally (Comment.retrieve + Thread.retrieve). This duplicates forum service calls (and course access checks) on every delete. Consider restructuring so the telemetry uses the data already fetched in delete_comment (e.g., have delete_comment return the cc_comment/context/course_id, or move the telemetry attribute setting into api.delete_comment where cc_comment is already available).
| cc_comment, course_id = _get_comment_trace_context(comment_id) | |
| set_custom_attribute("forum.course_id", str(course_id)) | |
| if cc_comment.get("parent_id"): | |
| set_custom_attribute( | |
| "forum.parent_comment_id", str(cc_comment.get("parent_id")) | |
| ) |
| except Exception as exc: | ||
| set_custom_attribute("forum.result", "error") | ||
| set_custom_attribute("forum.http_status", str(status.HTTP_400_BAD_REQUEST)) | ||
| set_custom_attribute("forum.error_type", _discussion_error_type(exc)) | ||
| raise |
There was a problem hiding this comment.
In the exception handler, forum.http_status is always recorded as 400, but this endpoint can raise exceptions that DRF will render with different status codes (e.g., PermissionDenied=403, UnsupportedMediaType=415, DiscussionBlackOutException=403). For more accurate telemetry, set forum.http_status based on the exception when available (e.g., getattr(exc, "status_code", 500) or mapping ObjectDoesNotExist->404) rather than hard-coding 400.
Implements COSMO2-853 for the comment create, comment update, and comment delete flows in edx-platform.
Adds Datadog forum telemetry for parent and child comment creation, comment updates, and comment deletes, including operation, entity context, actor, course, result, HTTP status, and relevant comment metadata.