ccproxy-api 0.1.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.
- ccproxy/__init__.py +4 -0
- ccproxy/__main__.py +7 -0
- ccproxy/_version.py +21 -0
- ccproxy/adapters/__init__.py +11 -0
- ccproxy/adapters/base.py +80 -0
- ccproxy/adapters/openai/__init__.py +43 -0
- ccproxy/adapters/openai/adapter.py +915 -0
- ccproxy/adapters/openai/models.py +412 -0
- ccproxy/adapters/openai/streaming.py +449 -0
- ccproxy/api/__init__.py +28 -0
- ccproxy/api/app.py +225 -0
- ccproxy/api/dependencies.py +140 -0
- ccproxy/api/middleware/__init__.py +11 -0
- ccproxy/api/middleware/auth.py +0 -0
- ccproxy/api/middleware/cors.py +55 -0
- ccproxy/api/middleware/errors.py +703 -0
- ccproxy/api/middleware/headers.py +51 -0
- ccproxy/api/middleware/logging.py +175 -0
- ccproxy/api/middleware/request_id.py +69 -0
- ccproxy/api/middleware/server_header.py +62 -0
- ccproxy/api/responses.py +84 -0
- ccproxy/api/routes/__init__.py +16 -0
- ccproxy/api/routes/claude.py +181 -0
- ccproxy/api/routes/health.py +489 -0
- ccproxy/api/routes/metrics.py +1033 -0
- ccproxy/api/routes/proxy.py +238 -0
- ccproxy/auth/__init__.py +75 -0
- ccproxy/auth/bearer.py +68 -0
- ccproxy/auth/credentials_adapter.py +93 -0
- ccproxy/auth/dependencies.py +229 -0
- ccproxy/auth/exceptions.py +79 -0
- ccproxy/auth/manager.py +102 -0
- ccproxy/auth/models.py +118 -0
- ccproxy/auth/oauth/__init__.py +26 -0
- ccproxy/auth/oauth/models.py +49 -0
- ccproxy/auth/oauth/routes.py +396 -0
- ccproxy/auth/oauth/storage.py +0 -0
- ccproxy/auth/storage/__init__.py +12 -0
- ccproxy/auth/storage/base.py +57 -0
- ccproxy/auth/storage/json_file.py +159 -0
- ccproxy/auth/storage/keyring.py +192 -0
- ccproxy/claude_sdk/__init__.py +20 -0
- ccproxy/claude_sdk/client.py +169 -0
- ccproxy/claude_sdk/converter.py +331 -0
- ccproxy/claude_sdk/options.py +120 -0
- ccproxy/cli/__init__.py +14 -0
- ccproxy/cli/commands/__init__.py +8 -0
- ccproxy/cli/commands/auth.py +553 -0
- ccproxy/cli/commands/config/__init__.py +14 -0
- ccproxy/cli/commands/config/commands.py +766 -0
- ccproxy/cli/commands/config/schema_commands.py +119 -0
- ccproxy/cli/commands/serve.py +630 -0
- ccproxy/cli/docker/__init__.py +34 -0
- ccproxy/cli/docker/adapter_factory.py +157 -0
- ccproxy/cli/docker/params.py +278 -0
- ccproxy/cli/helpers.py +144 -0
- ccproxy/cli/main.py +193 -0
- ccproxy/cli/options/__init__.py +14 -0
- ccproxy/cli/options/claude_options.py +216 -0
- ccproxy/cli/options/core_options.py +40 -0
- ccproxy/cli/options/security_options.py +48 -0
- ccproxy/cli/options/server_options.py +117 -0
- ccproxy/config/__init__.py +40 -0
- ccproxy/config/auth.py +154 -0
- ccproxy/config/claude.py +124 -0
- ccproxy/config/cors.py +79 -0
- ccproxy/config/discovery.py +87 -0
- ccproxy/config/docker_settings.py +265 -0
- ccproxy/config/loader.py +108 -0
- ccproxy/config/observability.py +158 -0
- ccproxy/config/pricing.py +88 -0
- ccproxy/config/reverse_proxy.py +31 -0
- ccproxy/config/scheduler.py +89 -0
- ccproxy/config/security.py +14 -0
- ccproxy/config/server.py +81 -0
- ccproxy/config/settings.py +534 -0
- ccproxy/config/validators.py +231 -0
- ccproxy/core/__init__.py +274 -0
- ccproxy/core/async_utils.py +675 -0
- ccproxy/core/constants.py +97 -0
- ccproxy/core/errors.py +256 -0
- ccproxy/core/http.py +328 -0
- ccproxy/core/http_transformers.py +428 -0
- ccproxy/core/interfaces.py +247 -0
- ccproxy/core/logging.py +189 -0
- ccproxy/core/middleware.py +114 -0
- ccproxy/core/proxy.py +143 -0
- ccproxy/core/system.py +38 -0
- ccproxy/core/transformers.py +259 -0
- ccproxy/core/types.py +129 -0
- ccproxy/core/validators.py +288 -0
- ccproxy/docker/__init__.py +67 -0
- ccproxy/docker/adapter.py +588 -0
- ccproxy/docker/docker_path.py +207 -0
- ccproxy/docker/middleware.py +103 -0
- ccproxy/docker/models.py +228 -0
- ccproxy/docker/protocol.py +192 -0
- ccproxy/docker/stream_process.py +264 -0
- ccproxy/docker/validators.py +173 -0
- ccproxy/models/__init__.py +123 -0
- ccproxy/models/errors.py +42 -0
- ccproxy/models/messages.py +243 -0
- ccproxy/models/requests.py +85 -0
- ccproxy/models/responses.py +227 -0
- ccproxy/models/types.py +102 -0
- ccproxy/observability/__init__.py +51 -0
- ccproxy/observability/access_logger.py +400 -0
- ccproxy/observability/context.py +447 -0
- ccproxy/observability/metrics.py +539 -0
- ccproxy/observability/pushgateway.py +366 -0
- ccproxy/observability/sse_events.py +303 -0
- ccproxy/observability/stats_printer.py +755 -0
- ccproxy/observability/storage/__init__.py +1 -0
- ccproxy/observability/storage/duckdb_simple.py +665 -0
- ccproxy/observability/storage/models.py +55 -0
- ccproxy/pricing/__init__.py +19 -0
- ccproxy/pricing/cache.py +212 -0
- ccproxy/pricing/loader.py +267 -0
- ccproxy/pricing/models.py +106 -0
- ccproxy/pricing/updater.py +309 -0
- ccproxy/scheduler/__init__.py +39 -0
- ccproxy/scheduler/core.py +335 -0
- ccproxy/scheduler/exceptions.py +34 -0
- ccproxy/scheduler/manager.py +186 -0
- ccproxy/scheduler/registry.py +150 -0
- ccproxy/scheduler/tasks.py +484 -0
- ccproxy/services/__init__.py +10 -0
- ccproxy/services/claude_sdk_service.py +614 -0
- ccproxy/services/credentials/__init__.py +55 -0
- ccproxy/services/credentials/config.py +105 -0
- ccproxy/services/credentials/manager.py +562 -0
- ccproxy/services/credentials/oauth_client.py +482 -0
- ccproxy/services/proxy_service.py +1536 -0
- ccproxy/static/.keep +0 -0
- ccproxy/testing/__init__.py +34 -0
- ccproxy/testing/config.py +148 -0
- ccproxy/testing/content_generation.py +197 -0
- ccproxy/testing/mock_responses.py +262 -0
- ccproxy/testing/response_handlers.py +161 -0
- ccproxy/testing/scenarios.py +241 -0
- ccproxy/utils/__init__.py +6 -0
- ccproxy/utils/cost_calculator.py +210 -0
- ccproxy/utils/streaming_metrics.py +199 -0
- ccproxy_api-0.1.0.dist-info/METADATA +253 -0
- ccproxy_api-0.1.0.dist-info/RECORD +148 -0
- ccproxy_api-0.1.0.dist-info/WHEEL +4 -0
- ccproxy_api-0.1.0.dist-info/entry_points.txt +2 -0
- ccproxy_api-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
"""Error handling middleware for CCProxy API Server."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
10
|
+
from structlog import get_logger
|
|
11
|
+
|
|
12
|
+
from ccproxy.core.errors import (
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
ClaudeProxyError,
|
|
15
|
+
DockerError,
|
|
16
|
+
MiddlewareError,
|
|
17
|
+
ModelNotFoundError,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
PermissionError,
|
|
20
|
+
ProxyAuthenticationError,
|
|
21
|
+
ProxyConnectionError,
|
|
22
|
+
ProxyError,
|
|
23
|
+
ProxyTimeoutError,
|
|
24
|
+
RateLimitError,
|
|
25
|
+
ServiceUnavailableError,
|
|
26
|
+
TimeoutError,
|
|
27
|
+
TransformationError,
|
|
28
|
+
ValidationError,
|
|
29
|
+
)
|
|
30
|
+
from ccproxy.observability.metrics import get_metrics
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
logger = get_logger(__name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def setup_error_handlers(app: FastAPI) -> None:
|
|
37
|
+
"""Setup error handlers for the FastAPI application.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
app: FastAPI application instance
|
|
41
|
+
"""
|
|
42
|
+
logger.debug("error_handlers_setup_start")
|
|
43
|
+
|
|
44
|
+
# Get metrics instance for error recording
|
|
45
|
+
try:
|
|
46
|
+
metrics = get_metrics()
|
|
47
|
+
logger.debug("error_handlers_metrics_loaded")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.warning("error_handlers_metrics_unavailable", error=str(e))
|
|
50
|
+
metrics = None
|
|
51
|
+
|
|
52
|
+
@app.exception_handler(ClaudeProxyError)
|
|
53
|
+
async def claude_proxy_error_handler(
|
|
54
|
+
request: Request, exc: ClaudeProxyError
|
|
55
|
+
) -> JSONResponse:
|
|
56
|
+
"""Handle Claude proxy specific errors."""
|
|
57
|
+
logger.error(
|
|
58
|
+
"Claude proxy error",
|
|
59
|
+
error_type="claude_proxy_error",
|
|
60
|
+
error_message=str(exc),
|
|
61
|
+
status_code=exc.status_code,
|
|
62
|
+
request_method=request.method,
|
|
63
|
+
request_url=str(request.url.path),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Record error in metrics
|
|
67
|
+
if metrics:
|
|
68
|
+
metrics.record_error(
|
|
69
|
+
error_type="claude_proxy_error",
|
|
70
|
+
endpoint=str(request.url.path),
|
|
71
|
+
model=None,
|
|
72
|
+
service_type="middleware",
|
|
73
|
+
)
|
|
74
|
+
return JSONResponse(
|
|
75
|
+
status_code=exc.status_code,
|
|
76
|
+
content={
|
|
77
|
+
"error": {
|
|
78
|
+
"type": exc.error_type,
|
|
79
|
+
"message": str(exc),
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@app.exception_handler(ValidationError)
|
|
85
|
+
async def validation_error_handler(
|
|
86
|
+
request: Request, exc: ValidationError
|
|
87
|
+
) -> JSONResponse:
|
|
88
|
+
"""Handle validation errors."""
|
|
89
|
+
logger.error(
|
|
90
|
+
"Validation error",
|
|
91
|
+
error_type="validation_error",
|
|
92
|
+
error_message=str(exc),
|
|
93
|
+
status_code=400,
|
|
94
|
+
request_method=request.method,
|
|
95
|
+
request_url=str(request.url.path),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Record error in metrics
|
|
99
|
+
if metrics:
|
|
100
|
+
metrics.record_error(
|
|
101
|
+
error_type="validation_error",
|
|
102
|
+
endpoint=str(request.url.path),
|
|
103
|
+
model=None,
|
|
104
|
+
service_type="middleware",
|
|
105
|
+
)
|
|
106
|
+
return JSONResponse(
|
|
107
|
+
status_code=400,
|
|
108
|
+
content={
|
|
109
|
+
"error": {
|
|
110
|
+
"type": "validation_error",
|
|
111
|
+
"message": str(exc),
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@app.exception_handler(AuthenticationError)
|
|
117
|
+
async def authentication_error_handler(
|
|
118
|
+
request: Request, exc: AuthenticationError
|
|
119
|
+
) -> JSONResponse:
|
|
120
|
+
"""Handle authentication errors."""
|
|
121
|
+
logger.error(
|
|
122
|
+
"Authentication error",
|
|
123
|
+
error_type="authentication_error",
|
|
124
|
+
error_message=str(exc),
|
|
125
|
+
status_code=401,
|
|
126
|
+
request_method=request.method,
|
|
127
|
+
request_url=str(request.url.path),
|
|
128
|
+
client_ip=request.client.host if request.client else "unknown",
|
|
129
|
+
user_agent=request.headers.get("user-agent", "unknown"),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Record error in metrics
|
|
133
|
+
if metrics:
|
|
134
|
+
metrics.record_error(
|
|
135
|
+
error_type="authentication_error",
|
|
136
|
+
endpoint=str(request.url.path),
|
|
137
|
+
model=None,
|
|
138
|
+
service_type="middleware",
|
|
139
|
+
)
|
|
140
|
+
return JSONResponse(
|
|
141
|
+
status_code=401,
|
|
142
|
+
content={
|
|
143
|
+
"error": {
|
|
144
|
+
"type": "authentication_error",
|
|
145
|
+
"message": str(exc),
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@app.exception_handler(PermissionError)
|
|
151
|
+
async def permission_error_handler(
|
|
152
|
+
request: Request, exc: PermissionError
|
|
153
|
+
) -> JSONResponse:
|
|
154
|
+
"""Handle permission errors."""
|
|
155
|
+
logger.error(
|
|
156
|
+
"Permission error",
|
|
157
|
+
error_type="permission_error",
|
|
158
|
+
error_message=str(exc),
|
|
159
|
+
status_code=403,
|
|
160
|
+
request_method=request.method,
|
|
161
|
+
request_url=str(request.url.path),
|
|
162
|
+
client_ip=request.client.host if request.client else "unknown",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Record error in metrics
|
|
166
|
+
if metrics:
|
|
167
|
+
metrics.record_error(
|
|
168
|
+
error_type="permission_error",
|
|
169
|
+
endpoint=str(request.url.path),
|
|
170
|
+
model=None,
|
|
171
|
+
service_type="middleware",
|
|
172
|
+
)
|
|
173
|
+
return JSONResponse(
|
|
174
|
+
status_code=403,
|
|
175
|
+
content={
|
|
176
|
+
"error": {
|
|
177
|
+
"type": "permission_error",
|
|
178
|
+
"message": str(exc),
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@app.exception_handler(NotFoundError)
|
|
184
|
+
async def not_found_error_handler(
|
|
185
|
+
request: Request, exc: NotFoundError
|
|
186
|
+
) -> JSONResponse:
|
|
187
|
+
"""Handle not found errors."""
|
|
188
|
+
logger.error(
|
|
189
|
+
"Not found error",
|
|
190
|
+
error_type="not_found_error",
|
|
191
|
+
error_message=str(exc),
|
|
192
|
+
status_code=404,
|
|
193
|
+
request_method=request.method,
|
|
194
|
+
request_url=str(request.url.path),
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Record error in metrics
|
|
198
|
+
if metrics:
|
|
199
|
+
metrics.record_error(
|
|
200
|
+
error_type="not_found_error",
|
|
201
|
+
endpoint=str(request.url.path),
|
|
202
|
+
model=None,
|
|
203
|
+
service_type="middleware",
|
|
204
|
+
)
|
|
205
|
+
return JSONResponse(
|
|
206
|
+
status_code=404,
|
|
207
|
+
content={
|
|
208
|
+
"error": {
|
|
209
|
+
"type": "not_found_error",
|
|
210
|
+
"message": str(exc),
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
@app.exception_handler(RateLimitError)
|
|
216
|
+
async def rate_limit_error_handler(
|
|
217
|
+
request: Request, exc: RateLimitError
|
|
218
|
+
) -> JSONResponse:
|
|
219
|
+
"""Handle rate limit errors."""
|
|
220
|
+
logger.error(
|
|
221
|
+
"Rate limit error",
|
|
222
|
+
error_type="rate_limit_error",
|
|
223
|
+
error_message=str(exc),
|
|
224
|
+
status_code=429,
|
|
225
|
+
request_method=request.method,
|
|
226
|
+
request_url=str(request.url.path),
|
|
227
|
+
client_ip=request.client.host if request.client else "unknown",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Record error in metrics
|
|
231
|
+
if metrics:
|
|
232
|
+
metrics.record_error(
|
|
233
|
+
error_type="rate_limit_error",
|
|
234
|
+
endpoint=str(request.url.path),
|
|
235
|
+
model=None,
|
|
236
|
+
service_type="middleware",
|
|
237
|
+
)
|
|
238
|
+
return JSONResponse(
|
|
239
|
+
status_code=429,
|
|
240
|
+
content={
|
|
241
|
+
"error": {
|
|
242
|
+
"type": "rate_limit_error",
|
|
243
|
+
"message": str(exc),
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
@app.exception_handler(ModelNotFoundError)
|
|
249
|
+
async def model_not_found_error_handler(
|
|
250
|
+
request: Request, exc: ModelNotFoundError
|
|
251
|
+
) -> JSONResponse:
|
|
252
|
+
"""Handle model not found errors."""
|
|
253
|
+
logger.error(
|
|
254
|
+
"Model not found error",
|
|
255
|
+
error_type="model_not_found_error",
|
|
256
|
+
error_message=str(exc),
|
|
257
|
+
status_code=404,
|
|
258
|
+
request_method=request.method,
|
|
259
|
+
request_url=str(request.url.path),
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Record error in metrics
|
|
263
|
+
if metrics:
|
|
264
|
+
metrics.record_error(
|
|
265
|
+
error_type="model_not_found_error",
|
|
266
|
+
endpoint=str(request.url.path),
|
|
267
|
+
model=None,
|
|
268
|
+
service_type="middleware",
|
|
269
|
+
)
|
|
270
|
+
return JSONResponse(
|
|
271
|
+
status_code=404,
|
|
272
|
+
content={
|
|
273
|
+
"error": {
|
|
274
|
+
"type": "model_not_found_error",
|
|
275
|
+
"message": str(exc),
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
@app.exception_handler(TimeoutError)
|
|
281
|
+
async def timeout_error_handler(
|
|
282
|
+
request: Request, exc: TimeoutError
|
|
283
|
+
) -> JSONResponse:
|
|
284
|
+
"""Handle timeout errors."""
|
|
285
|
+
logger.error(
|
|
286
|
+
"Timeout error",
|
|
287
|
+
error_type="timeout_error",
|
|
288
|
+
error_message=str(exc),
|
|
289
|
+
status_code=408,
|
|
290
|
+
request_method=request.method,
|
|
291
|
+
request_url=str(request.url.path),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Record error in metrics
|
|
295
|
+
if metrics:
|
|
296
|
+
metrics.record_error(
|
|
297
|
+
error_type="timeout_error",
|
|
298
|
+
endpoint=str(request.url.path),
|
|
299
|
+
model=None,
|
|
300
|
+
service_type="middleware",
|
|
301
|
+
)
|
|
302
|
+
return JSONResponse(
|
|
303
|
+
status_code=408,
|
|
304
|
+
content={
|
|
305
|
+
"error": {
|
|
306
|
+
"type": "timeout_error",
|
|
307
|
+
"message": str(exc),
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
@app.exception_handler(ServiceUnavailableError)
|
|
313
|
+
async def service_unavailable_error_handler(
|
|
314
|
+
request: Request, exc: ServiceUnavailableError
|
|
315
|
+
) -> JSONResponse:
|
|
316
|
+
"""Handle service unavailable errors."""
|
|
317
|
+
logger.error(
|
|
318
|
+
"Service unavailable error",
|
|
319
|
+
error_type="service_unavailable_error",
|
|
320
|
+
error_message=str(exc),
|
|
321
|
+
status_code=503,
|
|
322
|
+
request_method=request.method,
|
|
323
|
+
request_url=str(request.url.path),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Record error in metrics
|
|
327
|
+
if metrics:
|
|
328
|
+
metrics.record_error(
|
|
329
|
+
error_type="service_unavailable_error",
|
|
330
|
+
endpoint=str(request.url.path),
|
|
331
|
+
model=None,
|
|
332
|
+
service_type="middleware",
|
|
333
|
+
)
|
|
334
|
+
return JSONResponse(
|
|
335
|
+
status_code=503,
|
|
336
|
+
content={
|
|
337
|
+
"error": {
|
|
338
|
+
"type": "service_unavailable_error",
|
|
339
|
+
"message": str(exc),
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
@app.exception_handler(DockerError)
|
|
345
|
+
async def docker_error_handler(request: Request, exc: DockerError) -> JSONResponse:
|
|
346
|
+
"""Handle Docker errors."""
|
|
347
|
+
logger.error(
|
|
348
|
+
"Docker error",
|
|
349
|
+
error_type="docker_error",
|
|
350
|
+
error_message=str(exc),
|
|
351
|
+
status_code=500,
|
|
352
|
+
request_method=request.method,
|
|
353
|
+
request_url=str(request.url.path),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Record error in metrics
|
|
357
|
+
if metrics:
|
|
358
|
+
metrics.record_error(
|
|
359
|
+
error_type="docker_error",
|
|
360
|
+
endpoint=str(request.url.path),
|
|
361
|
+
model=None,
|
|
362
|
+
service_type="middleware",
|
|
363
|
+
)
|
|
364
|
+
return JSONResponse(
|
|
365
|
+
status_code=500,
|
|
366
|
+
content={
|
|
367
|
+
"error": {
|
|
368
|
+
"type": "docker_error",
|
|
369
|
+
"message": str(exc),
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Core proxy errors
|
|
375
|
+
@app.exception_handler(ProxyError)
|
|
376
|
+
async def proxy_error_handler(request: Request, exc: ProxyError) -> JSONResponse:
|
|
377
|
+
"""Handle proxy errors."""
|
|
378
|
+
logger.error(
|
|
379
|
+
"Proxy error",
|
|
380
|
+
error_type="proxy_error",
|
|
381
|
+
error_message=str(exc),
|
|
382
|
+
status_code=500,
|
|
383
|
+
request_method=request.method,
|
|
384
|
+
request_url=str(request.url.path),
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Record error in metrics
|
|
388
|
+
if metrics:
|
|
389
|
+
metrics.record_error(
|
|
390
|
+
error_type="proxy_error",
|
|
391
|
+
endpoint=str(request.url.path),
|
|
392
|
+
model=None,
|
|
393
|
+
service_type="middleware",
|
|
394
|
+
)
|
|
395
|
+
return JSONResponse(
|
|
396
|
+
status_code=500,
|
|
397
|
+
content={
|
|
398
|
+
"error": {
|
|
399
|
+
"type": "proxy_error",
|
|
400
|
+
"message": str(exc),
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
@app.exception_handler(TransformationError)
|
|
406
|
+
async def transformation_error_handler(
|
|
407
|
+
request: Request, exc: TransformationError
|
|
408
|
+
) -> JSONResponse:
|
|
409
|
+
"""Handle transformation errors."""
|
|
410
|
+
logger.error(
|
|
411
|
+
"Transformation error",
|
|
412
|
+
error_type="transformation_error",
|
|
413
|
+
error_message=str(exc),
|
|
414
|
+
status_code=500,
|
|
415
|
+
request_method=request.method,
|
|
416
|
+
request_url=str(request.url.path),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Record error in metrics
|
|
420
|
+
if metrics:
|
|
421
|
+
metrics.record_error(
|
|
422
|
+
error_type="transformation_error",
|
|
423
|
+
endpoint=str(request.url.path),
|
|
424
|
+
model=None,
|
|
425
|
+
service_type="middleware",
|
|
426
|
+
)
|
|
427
|
+
return JSONResponse(
|
|
428
|
+
status_code=500,
|
|
429
|
+
content={
|
|
430
|
+
"error": {
|
|
431
|
+
"type": "transformation_error",
|
|
432
|
+
"message": str(exc),
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
@app.exception_handler(MiddlewareError)
|
|
438
|
+
async def middleware_error_handler(
|
|
439
|
+
request: Request, exc: MiddlewareError
|
|
440
|
+
) -> JSONResponse:
|
|
441
|
+
"""Handle middleware errors."""
|
|
442
|
+
logger.error(
|
|
443
|
+
"Middleware error",
|
|
444
|
+
error_type="middleware_error",
|
|
445
|
+
error_message=str(exc),
|
|
446
|
+
status_code=500,
|
|
447
|
+
request_method=request.method,
|
|
448
|
+
request_url=str(request.url.path),
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
# Record error in metrics
|
|
452
|
+
if metrics:
|
|
453
|
+
metrics.record_error(
|
|
454
|
+
error_type="middleware_error",
|
|
455
|
+
endpoint=str(request.url.path),
|
|
456
|
+
model=None,
|
|
457
|
+
service_type="middleware",
|
|
458
|
+
)
|
|
459
|
+
return JSONResponse(
|
|
460
|
+
status_code=500,
|
|
461
|
+
content={
|
|
462
|
+
"error": {
|
|
463
|
+
"type": "middleware_error",
|
|
464
|
+
"message": str(exc),
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
@app.exception_handler(ProxyConnectionError)
|
|
470
|
+
async def proxy_connection_error_handler(
|
|
471
|
+
request: Request, exc: ProxyConnectionError
|
|
472
|
+
) -> JSONResponse:
|
|
473
|
+
"""Handle proxy connection errors."""
|
|
474
|
+
logger.error(
|
|
475
|
+
"Proxy connection error",
|
|
476
|
+
error_type="proxy_connection_error",
|
|
477
|
+
error_message=str(exc),
|
|
478
|
+
status_code=502,
|
|
479
|
+
request_method=request.method,
|
|
480
|
+
request_url=str(request.url.path),
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# Record error in metrics
|
|
484
|
+
if metrics:
|
|
485
|
+
metrics.record_error(
|
|
486
|
+
error_type="proxy_connection_error",
|
|
487
|
+
endpoint=str(request.url.path),
|
|
488
|
+
model=None,
|
|
489
|
+
service_type="middleware",
|
|
490
|
+
)
|
|
491
|
+
return JSONResponse(
|
|
492
|
+
status_code=502,
|
|
493
|
+
content={
|
|
494
|
+
"error": {
|
|
495
|
+
"type": "proxy_connection_error",
|
|
496
|
+
"message": str(exc),
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
@app.exception_handler(ProxyTimeoutError)
|
|
502
|
+
async def proxy_timeout_error_handler(
|
|
503
|
+
request: Request, exc: ProxyTimeoutError
|
|
504
|
+
) -> JSONResponse:
|
|
505
|
+
"""Handle proxy timeout errors."""
|
|
506
|
+
logger.error(
|
|
507
|
+
"Proxy timeout error",
|
|
508
|
+
error_type="proxy_timeout_error",
|
|
509
|
+
error_message=str(exc),
|
|
510
|
+
status_code=504,
|
|
511
|
+
request_method=request.method,
|
|
512
|
+
request_url=str(request.url.path),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Record error in metrics
|
|
516
|
+
if metrics:
|
|
517
|
+
metrics.record_error(
|
|
518
|
+
error_type="proxy_timeout_error",
|
|
519
|
+
endpoint=str(request.url.path),
|
|
520
|
+
model=None,
|
|
521
|
+
service_type="middleware",
|
|
522
|
+
)
|
|
523
|
+
return JSONResponse(
|
|
524
|
+
status_code=504,
|
|
525
|
+
content={
|
|
526
|
+
"error": {
|
|
527
|
+
"type": "proxy_timeout_error",
|
|
528
|
+
"message": str(exc),
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
@app.exception_handler(ProxyAuthenticationError)
|
|
534
|
+
async def proxy_authentication_error_handler(
|
|
535
|
+
request: Request, exc: ProxyAuthenticationError
|
|
536
|
+
) -> JSONResponse:
|
|
537
|
+
"""Handle proxy authentication errors."""
|
|
538
|
+
logger.error(
|
|
539
|
+
"Proxy authentication error",
|
|
540
|
+
error_type="proxy_authentication_error",
|
|
541
|
+
error_message=str(exc),
|
|
542
|
+
status_code=401,
|
|
543
|
+
request_method=request.method,
|
|
544
|
+
request_url=str(request.url.path),
|
|
545
|
+
client_ip=request.client.host if request.client else "unknown",
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
# Record error in metrics
|
|
549
|
+
if metrics:
|
|
550
|
+
metrics.record_error(
|
|
551
|
+
error_type="proxy_authentication_error",
|
|
552
|
+
endpoint=str(request.url.path),
|
|
553
|
+
model=None,
|
|
554
|
+
service_type="middleware",
|
|
555
|
+
)
|
|
556
|
+
return JSONResponse(
|
|
557
|
+
status_code=401,
|
|
558
|
+
content={
|
|
559
|
+
"error": {
|
|
560
|
+
"type": "proxy_authentication_error",
|
|
561
|
+
"message": str(exc),
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# Standard HTTP exceptions
|
|
567
|
+
@app.exception_handler(HTTPException)
|
|
568
|
+
async def http_exception_handler(
|
|
569
|
+
request: Request, exc: HTTPException
|
|
570
|
+
) -> JSONResponse:
|
|
571
|
+
"""Handle HTTP exceptions."""
|
|
572
|
+
# Don't log stack trace for 404 errors as they're expected
|
|
573
|
+
if exc.status_code == 404:
|
|
574
|
+
logger.info(
|
|
575
|
+
"HTTP 404 error",
|
|
576
|
+
error_type="http_404",
|
|
577
|
+
error_message=exc.detail,
|
|
578
|
+
status_code=404,
|
|
579
|
+
request_method=request.method,
|
|
580
|
+
request_url=str(request.url.path),
|
|
581
|
+
)
|
|
582
|
+
else:
|
|
583
|
+
# Log with basic stack trace (no local variables)
|
|
584
|
+
stack_trace = None
|
|
585
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
586
|
+
import traceback
|
|
587
|
+
|
|
588
|
+
stack_trace = traceback.format_exc()
|
|
589
|
+
|
|
590
|
+
logger.error(
|
|
591
|
+
"HTTP exception",
|
|
592
|
+
error_type="http_error",
|
|
593
|
+
error_message=exc.detail,
|
|
594
|
+
status_code=exc.status_code,
|
|
595
|
+
request_method=request.method,
|
|
596
|
+
request_url=str(request.url.path),
|
|
597
|
+
stack_trace=stack_trace,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# Record error in metrics
|
|
601
|
+
if metrics:
|
|
602
|
+
error_type = "http_404" if exc.status_code == 404 else "http_error"
|
|
603
|
+
metrics.record_error(
|
|
604
|
+
error_type=error_type,
|
|
605
|
+
endpoint=str(request.url.path),
|
|
606
|
+
model=None,
|
|
607
|
+
service_type="middleware",
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# TODO: Add when in prod hide details in response
|
|
611
|
+
return JSONResponse(
|
|
612
|
+
status_code=exc.status_code,
|
|
613
|
+
content={
|
|
614
|
+
"error": {
|
|
615
|
+
"type": "http_error",
|
|
616
|
+
"message": exc.detail,
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
@app.exception_handler(StarletteHTTPException)
|
|
622
|
+
async def starlette_http_exception_handler(
|
|
623
|
+
request: Request, exc: StarletteHTTPException
|
|
624
|
+
) -> JSONResponse:
|
|
625
|
+
"""Handle Starlette HTTP exceptions."""
|
|
626
|
+
# Don't log stack trace for 404 errors as they're expected
|
|
627
|
+
if exc.status_code == 404:
|
|
628
|
+
logger.info(
|
|
629
|
+
"Starlette HTTP 404 error",
|
|
630
|
+
error_type="starlette_http_404",
|
|
631
|
+
error_message=exc.detail,
|
|
632
|
+
status_code=404,
|
|
633
|
+
request_method=request.method,
|
|
634
|
+
request_url=str(request.url.path),
|
|
635
|
+
)
|
|
636
|
+
else:
|
|
637
|
+
logger.error(
|
|
638
|
+
"Starlette HTTP exception",
|
|
639
|
+
error_type="starlette_http_error",
|
|
640
|
+
error_message=exc.detail,
|
|
641
|
+
status_code=exc.status_code,
|
|
642
|
+
request_method=request.method,
|
|
643
|
+
request_url=str(request.url.path),
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Record error in metrics
|
|
647
|
+
if metrics:
|
|
648
|
+
error_type = (
|
|
649
|
+
"starlette_http_404"
|
|
650
|
+
if exc.status_code == 404
|
|
651
|
+
else "starlette_http_error"
|
|
652
|
+
)
|
|
653
|
+
metrics.record_error(
|
|
654
|
+
error_type=error_type,
|
|
655
|
+
endpoint=str(request.url.path),
|
|
656
|
+
model=None,
|
|
657
|
+
service_type="middleware",
|
|
658
|
+
)
|
|
659
|
+
return JSONResponse(
|
|
660
|
+
status_code=exc.status_code,
|
|
661
|
+
content={
|
|
662
|
+
"error": {
|
|
663
|
+
"type": "http_error",
|
|
664
|
+
"message": exc.detail,
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
# Global exception handler
|
|
670
|
+
@app.exception_handler(Exception)
|
|
671
|
+
async def global_exception_handler(
|
|
672
|
+
request: Request, exc: Exception
|
|
673
|
+
) -> JSONResponse:
|
|
674
|
+
"""Handle all other unhandled exceptions."""
|
|
675
|
+
logger.error(
|
|
676
|
+
"Unhandled exception",
|
|
677
|
+
error_type="unhandled_exception",
|
|
678
|
+
error_message=str(exc),
|
|
679
|
+
status_code=500,
|
|
680
|
+
request_method=request.method,
|
|
681
|
+
request_url=str(request.url.path),
|
|
682
|
+
exc_info=True,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# Record error in metrics
|
|
686
|
+
if metrics:
|
|
687
|
+
metrics.record_error(
|
|
688
|
+
error_type="unhandled_exception",
|
|
689
|
+
endpoint=str(request.url.path),
|
|
690
|
+
model=None,
|
|
691
|
+
service_type="middleware",
|
|
692
|
+
)
|
|
693
|
+
return JSONResponse(
|
|
694
|
+
status_code=500,
|
|
695
|
+
content={
|
|
696
|
+
"error": {
|
|
697
|
+
"type": "internal_server_error",
|
|
698
|
+
"message": "An internal server error occurred",
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
logger.debug("error_handlers_setup_completed")
|