exceptbot-drf 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exceptbot/__init__.py +1 -0
- exceptbot/admin.py +260 -0
- exceptbot/apps.py +7 -0
- exceptbot/middleware.py +126 -0
- exceptbot/migrations/0001_initial.py +63 -0
- exceptbot/migrations/__init__.py +0 -0
- exceptbot/models.py +134 -0
- exceptbot/permissions.py +11 -0
- exceptbot/serializers.py +65 -0
- exceptbot/tests.py +0 -0
- exceptbot/urls.py +85 -0
- exceptbot/views.py +272 -0
- exceptbot_drf-1.0.0.dist-info/METADATA +277 -0
- exceptbot_drf-1.0.0.dist-info/RECORD +17 -0
- exceptbot_drf-1.0.0.dist-info/WHEEL +5 -0
- exceptbot_drf-1.0.0.dist-info/licenses/LICENSE.md +49 -0
- exceptbot_drf-1.0.0.dist-info/top_level.txt +1 -0
exceptbot/urls.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
URL configuration for ExceptBot DRF API.
|
|
3
|
+
|
|
4
|
+
Endpoints:
|
|
5
|
+
GET /exceptbot/api/unresolved/
|
|
6
|
+
GET /exceptbot/api/resolved/
|
|
7
|
+
GET /exceptbot/api/settings/
|
|
8
|
+
PUT /exceptbot/api/settings/
|
|
9
|
+
GET /exceptbot/api/<log_id>/
|
|
10
|
+
GET /exceptbot/api/<log_id>/error/
|
|
11
|
+
GET /exceptbot/api/<log_id>/file/
|
|
12
|
+
POST /exceptbot/api/<log_id>/resolve/
|
|
13
|
+
POST /exceptbot/api/<log_id>/unresolve/
|
|
14
|
+
POST /exceptbot/api/<log_id>/ai/
|
|
15
|
+
"""
|
|
16
|
+
from django.urls import path
|
|
17
|
+
|
|
18
|
+
from .views import (
|
|
19
|
+
UnresolvedExceptionListView,
|
|
20
|
+
ResolvedExceptionListView,
|
|
21
|
+
ExceptionDetailView,
|
|
22
|
+
ExceptionErrorMessageView,
|
|
23
|
+
ExceptionFileContentView,
|
|
24
|
+
MarkResolvedView,
|
|
25
|
+
MarkUnresolvedView,
|
|
26
|
+
AIRecommendationView,
|
|
27
|
+
AppSettingsView,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
app_name = 'exceptbot'
|
|
31
|
+
|
|
32
|
+
urlpatterns = [
|
|
33
|
+
# ---------- لیستها ----------
|
|
34
|
+
path(
|
|
35
|
+
'api/unresolved/',
|
|
36
|
+
UnresolvedExceptionListView.as_view(),
|
|
37
|
+
name='unresolved-list',
|
|
38
|
+
),
|
|
39
|
+
path(
|
|
40
|
+
'api/resolved/',
|
|
41
|
+
ResolvedExceptionListView.as_view(),
|
|
42
|
+
name='resolved-list',
|
|
43
|
+
),
|
|
44
|
+
|
|
45
|
+
# ---------- تنظیمات ----------
|
|
46
|
+
path(
|
|
47
|
+
'api/settings/',
|
|
48
|
+
AppSettingsView.as_view(),
|
|
49
|
+
name='app-settings',
|
|
50
|
+
),
|
|
51
|
+
|
|
52
|
+
# ---------- جزئیات ----------
|
|
53
|
+
path(
|
|
54
|
+
'api/<int:log_id>/',
|
|
55
|
+
ExceptionDetailView.as_view(),
|
|
56
|
+
name='exception-detail',
|
|
57
|
+
),
|
|
58
|
+
path(
|
|
59
|
+
'api/<int:log_id>/error/',
|
|
60
|
+
ExceptionErrorMessageView.as_view(),
|
|
61
|
+
name='exception-error',
|
|
62
|
+
),
|
|
63
|
+
path(
|
|
64
|
+
'api/<int:log_id>/file/',
|
|
65
|
+
ExceptionFileContentView.as_view(),
|
|
66
|
+
name='exception-file',
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
# ---------- عملیات ----------
|
|
70
|
+
path(
|
|
71
|
+
'api/<int:log_id>/resolve/',
|
|
72
|
+
MarkResolvedView.as_view(),
|
|
73
|
+
name='mark-resolved',
|
|
74
|
+
),
|
|
75
|
+
path(
|
|
76
|
+
'api/<int:log_id>/unresolve/',
|
|
77
|
+
MarkUnresolvedView.as_view(),
|
|
78
|
+
name='mark-unresolved',
|
|
79
|
+
),
|
|
80
|
+
path(
|
|
81
|
+
'api/<int:log_id>/ai/',
|
|
82
|
+
AIRecommendationView.as_view(),
|
|
83
|
+
name='ai-recommendation',
|
|
84
|
+
),
|
|
85
|
+
]
|
exceptbot/views.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from rest_framework import status
|
|
2
|
+
from rest_framework.views import APIView
|
|
3
|
+
from rest_framework.response import Response
|
|
4
|
+
from rest_framework.generics import ListAPIView, RetrieveAPIView
|
|
5
|
+
|
|
6
|
+
from openai import OpenAI
|
|
7
|
+
|
|
8
|
+
from .models import ExceptionLog, AppSettings
|
|
9
|
+
from .permissions import IsSuperUser
|
|
10
|
+
from .serializers import (
|
|
11
|
+
ExceptionLogListSerializer,
|
|
12
|
+
ExceptionLogDetailSerializer,
|
|
13
|
+
ExceptionLogResolveSerializer,
|
|
14
|
+
AppSettingsSerializer,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ============================================================
|
|
19
|
+
# GET /exceptbot/api/unresolved/
|
|
20
|
+
# ============================================================
|
|
21
|
+
class UnresolvedExceptionListView(ListAPIView):
|
|
22
|
+
serializer_class = ExceptionLogListSerializer
|
|
23
|
+
permission_classes = [IsSuperUser]
|
|
24
|
+
|
|
25
|
+
def get_queryset(self):
|
|
26
|
+
return ExceptionLog.objects.filter(
|
|
27
|
+
is_resolved=False
|
|
28
|
+
).order_by('-timestamp')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ============================================================
|
|
32
|
+
# GET /exceptbot/api/resolved/
|
|
33
|
+
# ============================================================
|
|
34
|
+
class ResolvedExceptionListView(ListAPIView):
|
|
35
|
+
serializer_class = ExceptionLogListSerializer
|
|
36
|
+
permission_classes = [IsSuperUser]
|
|
37
|
+
|
|
38
|
+
def get_queryset(self):
|
|
39
|
+
return ExceptionLog.objects.filter(
|
|
40
|
+
is_resolved=True
|
|
41
|
+
).order_by('-timestamp')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ============================================================
|
|
45
|
+
# GET /exceptbot/api/<log_id>/
|
|
46
|
+
# ============================================================
|
|
47
|
+
class ExceptionDetailView(RetrieveAPIView):
|
|
48
|
+
queryset = ExceptionLog.objects.all()
|
|
49
|
+
serializer_class = ExceptionLogDetailSerializer
|
|
50
|
+
permission_classes = [IsSuperUser]
|
|
51
|
+
lookup_url_kwarg = 'log_id'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ============================================================
|
|
55
|
+
# GET /exceptbot/api/<log_id>/error/
|
|
56
|
+
# ============================================================
|
|
57
|
+
class ExceptionErrorMessageView(APIView):
|
|
58
|
+
permission_classes = [IsSuperUser]
|
|
59
|
+
|
|
60
|
+
def get(self, request, log_id):
|
|
61
|
+
try:
|
|
62
|
+
log = ExceptionLog.objects.get(id=log_id)
|
|
63
|
+
except ExceptionLog.DoesNotExist:
|
|
64
|
+
return Response(
|
|
65
|
+
{'detail': 'Exception log not found.'},
|
|
66
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return Response({
|
|
70
|
+
'id': log.id,
|
|
71
|
+
'exception_type': log.exception_type,
|
|
72
|
+
'full_error_message': log.full_error_message,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ============================================================
|
|
77
|
+
# GET /exceptbot/api/<log_id>/file/
|
|
78
|
+
# ============================================================
|
|
79
|
+
class ExceptionFileContentView(APIView):
|
|
80
|
+
permission_classes = [IsSuperUser]
|
|
81
|
+
|
|
82
|
+
def get(self, request, log_id):
|
|
83
|
+
try:
|
|
84
|
+
log = ExceptionLog.objects.get(id=log_id)
|
|
85
|
+
except ExceptionLog.DoesNotExist:
|
|
86
|
+
return Response(
|
|
87
|
+
{'detail': 'Exception log not found.'},
|
|
88
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return Response({
|
|
92
|
+
'id': log.id,
|
|
93
|
+
'file_name': log.file_name,
|
|
94
|
+
'line_number': log.line_number,
|
|
95
|
+
'error_line_content': log.error_line_content,
|
|
96
|
+
'file_content': log.file_content,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ============================================================
|
|
101
|
+
# POST /exceptbot/api/<log_id>/resolve/
|
|
102
|
+
# ============================================================
|
|
103
|
+
class MarkResolvedView(APIView):
|
|
104
|
+
permission_classes = [IsSuperUser]
|
|
105
|
+
|
|
106
|
+
def post(self, request, log_id):
|
|
107
|
+
try:
|
|
108
|
+
log = ExceptionLog.objects.get(id=log_id)
|
|
109
|
+
except ExceptionLog.DoesNotExist:
|
|
110
|
+
return Response(
|
|
111
|
+
{'detail': 'Exception log not found.'},
|
|
112
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
serializer = ExceptionLogResolveSerializer(data=request.data)
|
|
116
|
+
serializer.is_valid(raise_exception=True)
|
|
117
|
+
|
|
118
|
+
if log.is_resolved:
|
|
119
|
+
return Response(
|
|
120
|
+
{'detail': 'این خطا قبلاً رفع شده است.'},
|
|
121
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
log.mark_resolved(
|
|
125
|
+
user=request.user,
|
|
126
|
+
note=serializer.validated_data.get('resolution_note', ''),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return Response(
|
|
130
|
+
ExceptionLogDetailSerializer(log).data,
|
|
131
|
+
status=status.HTTP_200_OK,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ============================================================
|
|
136
|
+
# POST /exceptbot/api/<log_id>/unresolve/
|
|
137
|
+
# ============================================================
|
|
138
|
+
class MarkUnresolvedView(APIView):
|
|
139
|
+
permission_classes = [IsSuperUser]
|
|
140
|
+
|
|
141
|
+
def post(self, request, log_id):
|
|
142
|
+
try:
|
|
143
|
+
log = ExceptionLog.objects.get(id=log_id)
|
|
144
|
+
except ExceptionLog.DoesNotExist:
|
|
145
|
+
return Response(
|
|
146
|
+
{'detail': 'Exception log not found.'},
|
|
147
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
log.is_resolved = False
|
|
151
|
+
log.resolved_by = None
|
|
152
|
+
log.resolved_at = None
|
|
153
|
+
log.resolution_note = ''
|
|
154
|
+
log.save()
|
|
155
|
+
|
|
156
|
+
return Response(
|
|
157
|
+
ExceptionLogDetailSerializer(log).data,
|
|
158
|
+
status=status.HTTP_200_OK,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ============================================================
|
|
163
|
+
# POST /exceptbot/api/<log_id>/ai/
|
|
164
|
+
# ============================================================
|
|
165
|
+
class AIRecommendationView(APIView):
|
|
166
|
+
permission_classes = [IsSuperUser]
|
|
167
|
+
|
|
168
|
+
def post(self, request, log_id):
|
|
169
|
+
try:
|
|
170
|
+
log = ExceptionLog.objects.get(id=log_id)
|
|
171
|
+
except ExceptionLog.DoesNotExist:
|
|
172
|
+
return Response(
|
|
173
|
+
{'detail': 'Exception log not found.'},
|
|
174
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
app_settings = AppSettings.get_solo()
|
|
178
|
+
if not app_settings.openai_api_key:
|
|
179
|
+
return Response(
|
|
180
|
+
{'detail': 'OpenAI API key is not configured.'},
|
|
181
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
182
|
+
)
|
|
183
|
+
if log.ai_suggestion:
|
|
184
|
+
return Response({
|
|
185
|
+
'id': log.id,
|
|
186
|
+
'ai_suggestion': log.ai_suggestion,
|
|
187
|
+
'cached': True,
|
|
188
|
+
})
|
|
189
|
+
exception_information = f"""My Django application has thrown an exception.
|
|
190
|
+
An exception happened in line {log.line_number} of file {log.file_name}.
|
|
191
|
+
The exception type is: {log.exception_type}
|
|
192
|
+
|
|
193
|
+
The content of the exception message is:
|
|
194
|
+
{log.full_error_message}
|
|
195
|
+
|
|
196
|
+
text
|
|
197
|
+
|
|
198
|
+
The content of the file that produced the exception is:
|
|
199
|
+
{log.file_content}
|
|
200
|
+
|
|
201
|
+
text
|
|
202
|
+
|
|
203
|
+
The specific line from the above that threw the error is:
|
|
204
|
+
{log.error_line_content}
|
|
205
|
+
|
|
206
|
+
text
|
|
207
|
+
|
|
208
|
+
Please provide a suggestion for how to address the issue.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
messages = [
|
|
212
|
+
{
|
|
213
|
+
"role": "system",
|
|
214
|
+
"content": (
|
|
215
|
+
"You are ExceptBot. Users send you information about exceptions "
|
|
216
|
+
"in their Django applications and you respond with useful instructions "
|
|
217
|
+
"and code on how to fix the issue. Please answer efficiently and use "
|
|
218
|
+
"an economy of words."
|
|
219
|
+
),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"role": "user",
|
|
223
|
+
"content": exception_information,
|
|
224
|
+
},
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
client = OpenAI(api_key=app_settings.openai_api_key)
|
|
229
|
+
|
|
230
|
+
response = client.chat.completions.create(
|
|
231
|
+
model="gpt-4-1106-preview",
|
|
232
|
+
messages=messages,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
log.ai_suggestion = response.choices[0].message.content
|
|
236
|
+
log.save(update_fields=['ai_suggestion'])
|
|
237
|
+
|
|
238
|
+
except Exception as exc:
|
|
239
|
+
return Response(
|
|
240
|
+
{'detail': f'AI service error: {str(exc)}'},
|
|
241
|
+
status=status.HTTP_502_BAD_GATEWAY,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return Response({
|
|
245
|
+
'id': log.id,
|
|
246
|
+
'ai_suggestion': log.ai_suggestion,
|
|
247
|
+
'cached': False,
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ============================================================
|
|
252
|
+
# GET /exceptbot/api/settings/
|
|
253
|
+
# PUT /exceptbot/api/settings/
|
|
254
|
+
# ============================================================
|
|
255
|
+
class AppSettingsView(APIView):
|
|
256
|
+
permission_classes = [IsSuperUser]
|
|
257
|
+
|
|
258
|
+
def get(self, request):
|
|
259
|
+
app_settings = AppSettings.get_solo()
|
|
260
|
+
serializer = AppSettingsSerializer(app_settings)
|
|
261
|
+
return Response(serializer.data)
|
|
262
|
+
|
|
263
|
+
def put(self, request):
|
|
264
|
+
app_settings = AppSettings.get_solo()
|
|
265
|
+
serializer = AppSettingsSerializer(
|
|
266
|
+
app_settings,
|
|
267
|
+
data=request.data,
|
|
268
|
+
partial=True,
|
|
269
|
+
)
|
|
270
|
+
serializer.is_valid(raise_exception=True)
|
|
271
|
+
serializer.save()
|
|
272
|
+
return Response(serializer.data)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: exceptbot-drf
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Exception Logger with AI Suggestions for Django REST Framework
|
|
5
|
+
Author-email: Taha Zarei <tahazarei777@gmail.com>
|
|
6
|
+
Maintainer-email: Taha Zarei <tahazarei777@gmail.com>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/tahazarei777/exceptbot-drf
|
|
9
|
+
Project-URL: Repository, https://github.com/tahazarei777/exceptbot-drf.git
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/tahazarei777/exceptbot-drf/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/tahazarei777/exceptbot-drf/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: django,django-rest-framework,drf,exception,logger,error-tracking,middleware,openai,chatgpt,api
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 3.2
|
|
16
|
+
Classifier: Framework :: Django :: 4.0
|
|
17
|
+
Classifier: Framework :: Django :: 4.1
|
|
18
|
+
Classifier: Framework :: Django :: 4.2
|
|
19
|
+
Classifier: Framework :: Django :: 5.0
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: Python :: 3
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
30
|
+
Classifier: Topic :: System :: Logging
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE.md
|
|
34
|
+
Requires-Dist: Django>=3.2
|
|
35
|
+
Requires-Dist: djangorestframework>=3.12
|
|
36
|
+
Requires-Dist: openai>=1.3.7
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-django>=4.5.0; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
# ExceptBot DRF
|
|
46
|
+
|
|
47
|
+
Exception Logger with AI Suggestions for Django REST Framework.
|
|
48
|
+
|
|
49
|
+
[](https://pypi.org/project/exceptbot-drf/)
|
|
50
|
+
[](https://www.python.org/)
|
|
51
|
+
[](https://www.djangoproject.com/)
|
|
52
|
+
[](https://www.django-rest-framework.org/)
|
|
53
|
+
[](LICENSE.md)
|
|
54
|
+
|
|
55
|
+
## Overview
|
|
56
|
+
|
|
57
|
+
ExceptBot DRF is a Django middleware and REST API application that captures, logs, and helps resolve exceptions in Django/DRF projects. Superusers can view exceptions through REST endpoints, request AI-powered fix suggestions from OpenAI, and track resolution status.
|
|
58
|
+
|
|
59
|
+
This project is a fork of [ExceptBot](https://github.com/geneffects/exceptbot) by Brian Risk, modified to support Django REST Framework APIs, custom user models, and enhanced request metadata.
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- Automatic exception capture via Django middleware
|
|
64
|
+
- Full stack trace with code snapshot of the offending file
|
|
65
|
+
- File name, line number, and exact line that raised the exception
|
|
66
|
+
- Request context: URL path, HTTP method, status code, IP address, user agent, and masked request body
|
|
67
|
+
- Source detection (`backend`, `frontend`, `unknown`) via the `X-Client-Type` header
|
|
68
|
+
- Smart deduplication: identical exceptions increment a `count` field
|
|
69
|
+
- AI-powered fix suggestions via OpenAI ChatGPT
|
|
70
|
+
- Resolution tracking with `is_resolved`, `resolved_by`, `resolved_at`, and `resolution_note`
|
|
71
|
+
- REST API with superuser-only access
|
|
72
|
+
- Singleton `AppSettings` model
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
- Python >= 3.8
|
|
77
|
+
- Django >= 3.2
|
|
78
|
+
- djangorestframework >= 3.12
|
|
79
|
+
- openai >= 1.3.7
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install exceptbot-drf
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Setup
|
|
88
|
+
|
|
89
|
+
### 1. Add to `INSTALLED_APPS`
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
INSTALLED_APPS = [
|
|
93
|
+
# ...
|
|
94
|
+
'rest_framework',
|
|
95
|
+
'exceptbot',
|
|
96
|
+
# ...
|
|
97
|
+
]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2. Add the middleware
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
MIDDLEWARE = [
|
|
104
|
+
# ...
|
|
105
|
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
106
|
+
# ...
|
|
107
|
+
'exceptbot.middleware.ExceptBotMiddleware',
|
|
108
|
+
]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`AuthenticationMiddleware` must appear before `ExceptBotMiddleware` so that `request.user` is available.
|
|
112
|
+
|
|
113
|
+
### 3. Configure DRF authentication
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
REST_FRAMEWORK = {
|
|
117
|
+
'DEFAULT_AUTHENTICATION_CLASSES': [
|
|
118
|
+
'rest_framework.authentication.SessionAuthentication',
|
|
119
|
+
'rest_framework.authentication.TokenAuthentication',
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. Include the URLs
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from django.urls import path, include
|
|
128
|
+
|
|
129
|
+
urlpatterns = [
|
|
130
|
+
# ...
|
|
131
|
+
path('exceptbot/', include('exceptbot.urls', namespace='exceptbot')),
|
|
132
|
+
# ...
|
|
133
|
+
]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 5. Run migrations
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
python manage.py migrate exceptbot
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 6. Configure settings
|
|
143
|
+
|
|
144
|
+
Navigate to `/admin/exceptbot/appsettings/` and set the following fields:
|
|
145
|
+
|
|
146
|
+
| Field | Description |
|
|
147
|
+
| :--- | :--- |
|
|
148
|
+
| `project_name` | The directory name of your project (used to locate the correct file in the traceback) |
|
|
149
|
+
| `base_url` | Base URL of your site (e.g., `https://myapp.com`) |
|
|
150
|
+
| `openai_api_key` | OpenAI API key |
|
|
151
|
+
|
|
152
|
+
## API Endpoints
|
|
153
|
+
|
|
154
|
+
| Method | Endpoint | Description |
|
|
155
|
+
| :--- | :--- | :--- |
|
|
156
|
+
| GET | `/exceptbot/api/unresolved/` | List unresolved exceptions |
|
|
157
|
+
| GET | `/exceptbot/api/resolved/` | List resolved exceptions |
|
|
158
|
+
| GET | `/exceptbot/api/<id>/` | Full exception detail |
|
|
159
|
+
| GET | `/exceptbot/api/<id>/error/` | Error message and traceback |
|
|
160
|
+
| GET | `/exceptbot/api/<id>/file/` | Content of the offending file |
|
|
161
|
+
| POST | `/exceptbot/api/<id>/resolve/` | Mark as resolved |
|
|
162
|
+
| POST | `/exceptbot/api/<id>/unresolve/` | Revert to unresolved |
|
|
163
|
+
| POST | `/exceptbot/api/<id>/ai/` | Get or generate AI suggestion |
|
|
164
|
+
| GET | `/exceptbot/api/settings/` | View settings |
|
|
165
|
+
| PUT | `/exceptbot/api/settings/` | Update settings |
|
|
166
|
+
|
|
167
|
+
All endpoints require superuser authentication.
|
|
168
|
+
|
|
169
|
+
## Examples
|
|
170
|
+
|
|
171
|
+
Fetch unresolved exceptions:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
curl -H "Authorization: Bearer YOUR_TOKEN" \
|
|
175
|
+
http://localhost:8000/exceptbot/api/unresolved/
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Mark an exception as resolved:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
curl -X POST \
|
|
182
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
183
|
+
-H "Content-Type: application/json" \
|
|
184
|
+
-d '{"resolution_note": "Fixed in commit abc123"}' \
|
|
185
|
+
http://localhost:8000/exceptbot/api/1/resolve/
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Request an AI suggestion:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
curl -X POST \
|
|
192
|
+
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
193
|
+
http://localhost:8000/exceptbot/api/1/ai/
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Source Detection
|
|
197
|
+
|
|
198
|
+
To distinguish frontend errors from backend errors, include the following header in requests:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
X-Client-Type: frontend
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
If omitted, the default value is `backend`.
|
|
205
|
+
|
|
206
|
+
## How It Works
|
|
207
|
+
|
|
208
|
+
1. The middleware catches exceptions in `process_exception` and inspects the traceback.
|
|
209
|
+
2. It walks the traceback in reverse to find the first frame inside the project, matched by `project_name`.
|
|
210
|
+
3. It reads the offending file and stores the full source as a code snapshot.
|
|
211
|
+
4. It extracts HTTP method, status code, IP address, user agent, and masked request body.
|
|
212
|
+
5. Identical exceptions increment `count` instead of creating new records.
|
|
213
|
+
6. Superusers query the REST API to view, analyze, and resolve exceptions.
|
|
214
|
+
|
|
215
|
+
## Security
|
|
216
|
+
|
|
217
|
+
- Sensitive request fields (`password`, `token`, `api_key`, `authorization`, and similar) are replaced with `***MASKED***` before being stored.
|
|
218
|
+
- All endpoints are protected by the `IsSuperUser` permission class.
|
|
219
|
+
- The OpenAI API key is hidden in the admin list view and only shown as a boolean.
|
|
220
|
+
- Only one `AppSettings` record can exist.
|
|
221
|
+
|
|
222
|
+
## Project Structure
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
exceptbot/
|
|
226
|
+
├── migrations/
|
|
227
|
+
├── admin.py
|
|
228
|
+
├── apps.py
|
|
229
|
+
├── middleware.py
|
|
230
|
+
├── models.py
|
|
231
|
+
├── permissions.py
|
|
232
|
+
├── serializers.py
|
|
233
|
+
├── urls.py
|
|
234
|
+
└── views.py
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Issues and pull requests are welcome at:
|
|
240
|
+
https://github.com/tahazarei777/exceptbot-drf
|
|
241
|
+
|
|
242
|
+
## Reporting Issues
|
|
243
|
+
|
|
244
|
+
Report bugs at:
|
|
245
|
+
https://github.com/tahazarei777/exceptbot-drf/issues
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
This project is a fork of [ExceptBot](https://github.com/geneffects/exceptbot) by Brian Risk.
|
|
250
|
+
|
|
251
|
+
Original work: Copyright © 2023-present, [D.AT Analytics, LLC](https://d.at/). All rights reserved.
|
|
252
|
+
|
|
253
|
+
Modifications: Copyright © 2025, Taha Zarei.
|
|
254
|
+
|
|
255
|
+
Licensed under the BSD 3-Clause License. See [LICENSE.md](LICENSE.md) for details.
|
|
256
|
+
Exception Logger with AI Suggestions for Django REST Framework
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Website:**
|
|
260
|
+
```
|
|
261
|
+
https://pypi.org/project/exceptbot-drf/
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Topics:**
|
|
265
|
+
```
|
|
266
|
+
django
|
|
267
|
+
django-rest-framework
|
|
268
|
+
drf
|
|
269
|
+
exception-logger
|
|
270
|
+
error-tracking
|
|
271
|
+
openai
|
|
272
|
+
chatgpt
|
|
273
|
+
python
|
|
274
|
+
middleware
|
|
275
|
+
api
|
|
276
|
+
```
|
|
277
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
exceptbot/__init__.py,sha256=qpinTa2JOwKaCmaZUtKaIYyL7I-mmma7s6Aw-zn2yWU,53
|
|
2
|
+
exceptbot/admin.py,sha256=Wl6ZHlHECwQG8BqdGGKTS_2BxB0BNSWouGycZpW5SeE,7657
|
|
3
|
+
exceptbot/apps.py,sha256=dm-qZBaqJf9KUtque9jPOFTdTojLLwXWaAVD9w7Z2wc,224
|
|
4
|
+
exceptbot/middleware.py,sha256=XmPDVBtzKj0CwRMHp8F6CmG-4HxZ3PqHj618u0HkLnY,4759
|
|
5
|
+
exceptbot/models.py,sha256=F3qxNdxMNXqDVTiIu5kf_S04AsQYvb2f-rcrR6dMiBU,4588
|
|
6
|
+
exceptbot/permissions.py,sha256=ifz3DSAQ3Btv23N8v5liOv_wrhqSvKFpwvB2NFyLC0k,353
|
|
7
|
+
exceptbot/serializers.py,sha256=7HwGNWpaP0AvWDRvFgB7PcXRNMvj6zLUAhabvaSIrM4,1812
|
|
8
|
+
exceptbot/tests.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
exceptbot/urls.py,sha256=6NMAd96zoNezSvGZbV3bXQY_MIVSUBr5raBQNBm3OCs,2117
|
|
10
|
+
exceptbot/views.py,sha256=dAeEXN5dOvAzQG3k0WKdwu8WMwqVyioKWEnByzjcMD8,8895
|
|
11
|
+
exceptbot/migrations/0001_initial.py,sha256=vGj62lOh87tIA5E6dXyapcGUhv3qu56-yv_nMsFilCU,3916
|
|
12
|
+
exceptbot/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
exceptbot_drf-1.0.0.dist-info/licenses/LICENSE.md,sha256=jCLL2JKM45eawXN10hanKHEo1VBGbMcWMH6hCpmE_QU,2388
|
|
14
|
+
exceptbot_drf-1.0.0.dist-info/METADATA,sha256=-EuyI0wfelURRf9ADaP8eEleac9RQR4Rdbb0s7zGOkQ,8775
|
|
15
|
+
exceptbot_drf-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
16
|
+
exceptbot_drf-1.0.0.dist-info/top_level.txt,sha256=KMKHsV3dV3nn3xSzh8DeAasL87GKqVPubPFvCMiedQI,10
|
|
17
|
+
exceptbot_drf-1.0.0.dist-info/RECORD,,
|