feat: enhance tracing with HTTP client instrumentation and UI improvement#25
feat: enhance tracing with HTTP client instrumentation and UI improvement#25qinsehm1128 wants to merge 2 commits intodoganarif:mainfrom
Conversation
…ments
This commit introduces three major improvements to the tracing system:
1. **HTTP Client Instrumentation**
- Add automatic tracing for outbound HTTP requests
- Support for requests, httpx (sync/async), and aiohttp clients
- Capture HTTP method, URL, host, status code, and response size
- Automatic span creation and error tracking for HTTP calls
2. **Tracing UI Localization**
- Add complete i18n support for TracesList component
- Translate search placeholder, status filters, time ranges
- Translate error messages and empty states
- Support both English and Chinese languages
3. **Search Functionality**
- Add search parameter to /api/traces endpoint
- Enable filtering traces by operation name (ILIKE)
- Wire up search input in frontend to backend API
- Update TypeScript client with search parameter support
Technical changes:
- Instrument HTTP clients via monkey-patching in tracing.py
- Refactor get_waterfall_data to use session factory pattern
- Fix DuckDB transaction conflicts in trace detail endpoints
Reviewer's GuideThis PR enhances the tracing system by integrating automatic HTTP client instrumentation, refactoring session management for waterfall data queries, adding search capabilities both in the API and UI, and fully localizing the tracing interface for English and Chinese. Sequence diagram for automatic HTTP client instrumentation in tracingsequenceDiagram
participant App
participant "requests/httpx/aiohttp"
participant Tracing
App->>"requests/httpx/aiohttp": Make HTTP request
activate "requests/httpx/aiohttp"
"requests/httpx/aiohttp"->>Tracing: _create_http_span(method, url)
Tracing-->>"requests/httpx/aiohttp": span_id
"requests/httpx/aiohttp"->>"requests/httpx/aiohttp": Perform HTTP request
alt Success
"requests/httpx/aiohttp"->>Tracing: _finish_http_span(span_id, response)
else Error
"requests/httpx/aiohttp"->>Tracing: _finish_http_span(span_id, error)
end
"requests/httpx/aiohttp"-->>App: Return response/error
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- Use FastAPI’s dependency injection for DB sessions instead of manually creating and closing SessionLocal in each endpoint to reduce boilerplate and improve safety.
- Replace ad-hoc print and commented-out debug statements with a structured logger to keep production code clean and configurable.
- Consider refactoring get_waterfall_data to accept a Session instance rather than a session factory for a more intuitive and consistent API.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Use FastAPI’s dependency injection for DB sessions instead of manually creating and closing SessionLocal in each endpoint to reduce boilerplate and improve safety.
- Replace ad-hoc print and commented-out debug statements with a structured logger to keep production code clean and configurable.
- Consider refactoring get_waterfall_data to accept a Session instance rather than a session factory for a more intuitive and consistent API.
## Individual Comments
### Comment 1
<location> `fastapi_radar/tracing.py:264-265` </location>
<code_context>
+ return rows
+
+ except Exception as e:
+ print(f"[ERROR] Exception in get_waterfall_data: {str(e)}")
+ print(f"[ERROR] Traceback: {traceback.format_exc()}")
+ session.rollback()
+ raise
</code_context>
<issue_to_address>
**suggestion:** Error logging uses print statements.
Switch to a logging framework to improve error tracking and observability in production.
Suggested implementation:
```python
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Exception in get_waterfall_data: {str(e)}")
logger.error(f"Traceback: {traceback.format_exc()}")
session.rollback()
raise
```
If logging is already configured globally in your project, you can remove the `import logging` and `logger = logging.getLogger(__name__)` lines and use the existing logger instance. If not, consider configuring logging at the application entry point for consistent log formatting and output.
</issue_to_address>
### Comment 2
<location> `fastapi_radar/api.py:458-459` </location>
<code_context>
+ except HTTPException:
+ raise # 直接抛出 HTTPException
+ except Exception as e:
+ print(f"[ERROR] Exception in get_trace_detail: {str(e)}")
+ print(f"[ERROR] Traceback: {traceback.format_exc()}")
+ session.rollback()
+ raise HTTPException(status_code=500, detail=str(e))
</code_context>
<issue_to_address>
**suggestion:** Error logging uses print statements in API endpoints.
Use a logging library instead of print statements to handle errors more effectively in production environments.
Suggested implementation:
```python
import logging
logging.error(f"Exception in get_trace_detail: {str(e)}")
logging.error(f"Traceback: {traceback.format_exc()}")
```
If `logging` is already imported at the top of the file, you can remove the `import logging` line from inside the function.
For production readiness, consider configuring the logging level and handlers at the application entry point (not shown in this snippet).
</issue_to_address>
### Comment 3
<location> `fastapi_radar/api.py:455-463` </location>
<code_context>
+ created_at=trace.created_at,
+ spans=[WaterfallSpan(**span) for span in waterfall_spans],
+ )
+ except HTTPException:
+ raise # 直接抛出 HTTPException
+ except Exception as e:
+ print(f"[ERROR] Exception in get_trace_detail: {str(e)}")
</code_context>
<issue_to_address>
**suggestion:** Catching and re-raising HTTPException is redundant.
Remove the except block for HTTPException and allow it to propagate without catching.
```suggestion
except Exception as e:
print(f"[ERROR] Exception in get_trace_detail: {str(e)}")
print(f"[ERROR] Traceback: {traceback.format_exc()}")
session.rollback()
raise HTTPException(status_code=500, detail=str(e))
finally:
session.close()
```
</issue_to_address>
### Comment 4
<location> `fastapi_radar/dashboard/src/components/TracesList.tsx:68` </location>
<code_context>
export function TracesList({ className }: TracesListProps) {
const { openDetail } = useDetailDrawer();
+ const t = useT();
const [filters, setFilters] = useState({
</code_context>
<issue_to_address>
**suggestion:** Internationalization is applied to UI strings.
Please check that all user-facing strings, including hardcoded fallbacks like 'Unknown', use the translation function for complete localization.
Suggested implementation:
```typescript
<div className="text-center">
<AlertTriangle className="h-12 w-12 text-destructive mx-auto mb-4" />
```
```typescript
<p className="text-muted-foreground">{t("no_traces_found", "No traces found")}</p>
```
```typescript
<span className="font-mono text-xs text-muted-foreground">
{trace.service_name || t("unknown", "Unknown")}
</span>
```
```typescript
<span className="font-mono text-xs text-muted-foreground">
{trace.operation_name || t("unknown", "Unknown")}
</span>
```
</issue_to_address>
### Comment 5
<location> `fastapi_radar/tracing.py:366` </location>
<code_context>
trace_ctx.finish_span(span_id, status=status, tags=tags if tags else None)
</code_context>
<issue_to_address>
**suggestion (code-quality):** Replace if-expression with `or` ([`or-if-exp-identity`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/or-if-exp-identity))
```suggestion
trace_ctx.finish_span(span_id, status=status, tags=tags or None)
```
<br/><details><summary>Explanation</summary>Here we find ourselves setting a value if it evaluates to `True`, and otherwise
using a default.
The 'After' case is a bit easier to read and avoids the duplication of
`input_currency`.
It works because the left-hand side is evaluated first. If it evaluates to
true then `currency` will be set to this and the right-hand side will not be
evaluated. If it evaluates to false the right-hand side will be evaluated and
`currency` will be set to `DEFAULT_CURRENCY`.
</details>
</issue_to_address>
### Comment 6
<location> `fastapi_radar/tracing.py:239` </location>
<code_context>
@classmethod
def get_waterfall_data(
cls, session_local, trace_id: str
) -> List[Dict[str, Any]]:
"""Return data for the waterfall view using the provided session."""
#print(f"[DEBUG] get_waterfall_data called with trace_id: {trace_id}")
waterfall_query = text(
"""
WITH span_timeline AS (
SELECT
s.span_id,
s.parent_span_id,
s.operation_name,
s.service_name,
s.start_time,
s.end_time,
s.duration_ms,
s.status,
s.tags,
COALESCE(r.depth, 0) as depth,
-- Offset relative to trace start
EXTRACT(EPOCH FROM (
s.start_time - MIN(s.start_time)
OVER (PARTITION BY s.trace_id)
)) * 1000 as offset_ms
FROM radar_spans s
LEFT JOIN radar_span_relations r ON s.span_id = r.child_span_id
WHERE s.trace_id = :trace_id
)
SELECT * FROM span_timeline
ORDER BY offset_ms, depth
"""
)
#print(f"[DEBUG] Getting SessionLocal from session_local function")
SessionLocal = session_local() # 获取SessionLocal (sessionmaker)
#print(f"[DEBUG] SessionLocal type: {type(SessionLocal)}")
session = SessionLocal() # 创建实际的session实例
#print(f"[DEBUG] Session created, type: {type(session)}")
try:
#print(f"[DEBUG] Executing waterfall query for trace_id: {trace_id}")
result = session.execute(waterfall_query, {"trace_id": trace_id})
#print(f"[DEBUG] Query executed successfully")
# 不需要 commit,因为这是 SELECT 查询
rows = []
for row in result:
rows.append({
"span_id": row.span_id,
"parent_span_id": row.parent_span_id,
"operation_name": row.operation_name,
"service_name": row.service_name,
"start_time": row.start_time.isoformat() if row.start_time else None,
"end_time": row.end_time.isoformat() if row.end_time else None,
"duration_ms": row.duration_ms,
"status": row.status,
"tags": row.tags,
"depth": row.depth,
"offset_ms": float(row.offset_ms) if row.offset_ms else 0.0,
})
#print(f"[DEBUG] Processed {len(rows)} rows from query result")
return rows
except Exception as e:
print(f"[ERROR] Exception in get_waterfall_data: {str(e)}")
print(f"[ERROR] Traceback: {traceback.format_exc()}")
session.rollback()
raise
finally:
#print(f"[DEBUG] Closing session")
session.close()
</code_context>
<issue_to_address>
**issue (code-quality):** We've found these issues:
- Convert for loop into list comprehension ([`list-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/list-comprehension/))
- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))
</issue_to_address>
### Comment 7
<location> `fastapi_radar/tracing.py:299-300` </location>
<code_context>
def _create_http_span(
method: Optional[str], url: Optional[str]
) -> Tuple[Optional["TraceContext"], Optional[str]]:
trace_ctx = get_current_trace_context()
if not trace_ctx:
return None, None
method_name = str(method).upper() if method else "GET"
url_value = str(url) if url is not None else ""
tags: Dict[str, Any] = {
"component": "http",
"http.method": method_name,
"http.url": url_value,
}
host = _extract_http_host(url_value)
if host:
tags["http.host"] = host
span_id = trace_ctx.create_span(
operation_name=f"HTTP {method_name}",
span_kind="client",
tags=tags,
)
return trace_ctx, span_id
</code_context>
<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
```suggestion
if host := _extract_http_host(url_value):
```
</issue_to_address>
### Comment 8
<location> `fastapi_radar/tracing.py:351-352` </location>
<code_context>
def _finish_http_span(
trace_ctx: Optional["TraceContext"],
span_id: Optional[str],
response: Any = None,
error: Optional[BaseException] = None,
) -> None:
if not trace_ctx or not span_id:
return
tags: Dict[str, Any] = {}
status = "ok"
if response is not None:
status_code = _extract_status_code(response)
if status_code is not None:
tags["http.status_code"] = status_code
content_length = _extract_content_length(response)
if content_length:
tags["http.response_content_length"] = content_length
if error is not None:
status = "error"
tags["error.type"] = type(error).__name__
tags["error.message"] = str(error)
trace_ctx.add_span_log(
span_id,
f"HTTP request failed: {error}",
level="error",
exception_type=type(error).__name__,
)
trace_ctx.finish_span(span_id, status=status, tags=tags if tags else None)
</code_context>
<issue_to_address>
**suggestion (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
```suggestion
if content_length := _extract_content_length(response):
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| print(f"[ERROR] Exception in get_waterfall_data: {str(e)}") | ||
| print(f"[ERROR] Traceback: {traceback.format_exc()}") |
There was a problem hiding this comment.
suggestion: Error logging uses print statements.
Switch to a logging framework to improve error tracking and observability in production.
Suggested implementation:
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Exception in get_waterfall_data: {str(e)}")
logger.error(f"Traceback: {traceback.format_exc()}")
session.rollback()
raiseIf logging is already configured globally in your project, you can remove the import logging and logger = logging.getLogger(__name__) lines and use the existing logger instance. If not, consider configuring logging at the application entry point for consistent log formatting and output.
| print(f"[ERROR] Exception in get_trace_detail: {str(e)}") | ||
| print(f"[ERROR] Traceback: {traceback.format_exc()}") |
There was a problem hiding this comment.
suggestion: Error logging uses print statements in API endpoints.
Use a logging library instead of print statements to handle errors more effectively in production environments.
Suggested implementation:
import logging
logging.error(f"Exception in get_trace_detail: {str(e)}")
logging.error(f"Traceback: {traceback.format_exc()}")If logging is already imported at the top of the file, you can remove the import logging line from inside the function.
For production readiness, consider configuring the logging level and handlers at the application entry point (not shown in this snippet).
| except HTTPException: | ||
| raise # 直接抛出 HTTPException | ||
| except Exception as e: | ||
| print(f"[ERROR] Exception in get_trace_detail: {str(e)}") | ||
| print(f"[ERROR] Traceback: {traceback.format_exc()}") | ||
| session.rollback() | ||
| raise HTTPException(status_code=500, detail=str(e)) | ||
| finally: | ||
| session.close() |
There was a problem hiding this comment.
suggestion: Catching and re-raising HTTPException is redundant.
Remove the except block for HTTPException and allow it to propagate without catching.
| except HTTPException: | |
| raise # 直接抛出 HTTPException | |
| except Exception as e: | |
| print(f"[ERROR] Exception in get_trace_detail: {str(e)}") | |
| print(f"[ERROR] Traceback: {traceback.format_exc()}") | |
| session.rollback() | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| finally: | |
| session.close() | |
| except Exception as e: | |
| print(f"[ERROR] Exception in get_trace_detail: {str(e)}") | |
| print(f"[ERROR] Traceback: {traceback.format_exc()}") | |
| session.rollback() | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| finally: | |
| session.close() |
|
|
||
| export function TracesList({ className }: TracesListProps) { | ||
| const { openDetail } = useDetailDrawer(); | ||
| const t = useT(); |
There was a problem hiding this comment.
suggestion: Internationalization is applied to UI strings.
Please check that all user-facing strings, including hardcoded fallbacks like 'Unknown', use the translation function for complete localization.
Suggested implementation:
<div className="text-center">
<AlertTriangle className="h-12 w-12 text-destructive mx-auto mb-4" /> <p className="text-muted-foreground">{t("no_traces_found", "No traces found")}</p> <span className="font-mono text-xs text-muted-foreground">
{trace.service_name || t("unknown", "Unknown")}
</span> <span className="font-mono text-xs text-muted-foreground">
{trace.operation_name || t("unknown", "Unknown")}
</span>| exception_type=type(error).__name__, | ||
| ) | ||
|
|
||
| trace_ctx.finish_span(span_id, status=status, tags=tags if tags else None) |
There was a problem hiding this comment.
suggestion (code-quality): Replace if-expression with or (or-if-exp-identity)
| trace_ctx.finish_span(span_id, status=status, tags=tags if tags else None) | |
| trace_ctx.finish_span(span_id, status=status, tags=tags or None) |
Explanation
Here we find ourselves setting a value if it evaluates toTrue, and otherwiseusing a default.
The 'After' case is a bit easier to read and avoids the duplication of
input_currency.
It works because the left-hand side is evaluated first. If it evaluates to
true then currency will be set to this and the right-hand side will not be
evaluated. If it evaluates to false the right-hand side will be evaluated and
currency will be set to DEFAULT_CURRENCY.
| host = _extract_http_host(url_value) | ||
| if host: |
There was a problem hiding this comment.
suggestion (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)
| host = _extract_http_host(url_value) | |
| if host: | |
| if host := _extract_http_host(url_value): |
| content_length = _extract_content_length(response) | ||
| if content_length: |
There was a problem hiding this comment.
suggestion (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)
| content_length = _extract_content_length(response) | |
| if content_length: | |
| if content_length := _extract_content_length(response): |
There was a problem hiding this comment.
3 issues found across 8 files
Prompt for AI agents (all 3 issues)
Understand the root cause of the following 3 issues and fix them.
<file name="tests/test_async_radar.py">
<violation number="1" location="tests/test_async_radar.py:50">
This synchronous HTTP client call runs inside an async FastAPI handler, so it blocks the event loop until the network request finishes. Use an async client (e.g. `httpx.AsyncClient`) or offload to a thread instead.</violation>
<violation number="2" location="tests/test_async_radar.py:51">
This test now depends on reaching http://www.baidu.com, which makes it flaky in offline or firewalled environments. Replace the external call with a local mock or fixture so the test stays deterministic.</violation>
</file>
<file name="fastapi_radar/dashboard/src/components/TracesList.tsx">
<violation number="1" location="fastapi_radar/dashboard/src/components/TracesList.tsx:230">
Localize the fallback string instead of hardcoding "Unknown" so the UI remains fully translated (e.g., use t("pages.tracing.unknown", "Unknown")).</violation>
</file>
React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.
| rows = result.mappings().all() | ||
| import httpx,requests | ||
| res1 = httpx.get("http://www.baidu.com") | ||
| res2 = requests.get("http://www.baidu.com") |
There was a problem hiding this comment.
This test now depends on reaching http://www.baidu.com, which makes it flaky in offline or firewalled environments. Replace the external call with a local mock or fixture so the test stays deterministic.
Prompt for AI agents
Address the following comment on tests/test_async_radar.py at line 51:
<comment>This test now depends on reaching http://www.baidu.com, which makes it flaky in offline or firewalled environments. Replace the external call with a local mock or fixture so the test stays deterministic.</comment>
<file context>
@@ -46,6 +46,9 @@ async def get_users():
rows = result.mappings().all()
+ import httpx,requests
+ res1 = httpx.get("http://www.baidu.com")
+ res2 = requests.get("http://www.baidu.com")
return {"users": [dict(row) for row in rows]}
</file context>
| result = await session.execute(select(users_table)) | ||
| rows = result.mappings().all() | ||
| import httpx,requests | ||
| res1 = httpx.get("http://www.baidu.com") |
There was a problem hiding this comment.
This synchronous HTTP client call runs inside an async FastAPI handler, so it blocks the event loop until the network request finishes. Use an async client (e.g. httpx.AsyncClient) or offload to a thread instead.
Prompt for AI agents
Address the following comment on tests/test_async_radar.py at line 50:
<comment>This synchronous HTTP client call runs inside an async FastAPI handler, so it blocks the event loop until the network request finishes. Use an async client (e.g. `httpx.AsyncClient`) or offload to a thread instead.</comment>
<file context>
@@ -46,6 +46,9 @@ async def get_users():
result = await session.execute(select(users_table))
rows = result.mappings().all()
+ import httpx,requests
+ res1 = httpx.get("http://www.baidu.com")
+ res2 = requests.get("http://www.baidu.com")
</file context>
| <div className="flex items-center gap-4 text-sm text-muted-foreground"> | ||
| <span>Service: {trace.service_name || "Unknown"}</span> | ||
| <span>Spans: {trace.span_count}</span> | ||
| <span>{t("pages.tracing.service")}: {trace.service_name || "Unknown"}</span> |
There was a problem hiding this comment.
Localize the fallback string instead of hardcoding "Unknown" so the UI remains fully translated (e.g., use t("pages.tracing.unknown", "Unknown")).
Prompt for AI agents
Address the following comment on fastapi_radar/dashboard/src/components/TracesList.tsx at line 230:
<comment>Localize the fallback string instead of hardcoding "Unknown" so the UI remains fully translated (e.g., use t("pages.tracing.unknown", "Unknown")).</comment>
<file context>
@@ -224,10 +227,10 @@ export function TracesList({ className }: TracesListProps) {
<div className="flex items-center gap-4 text-sm text-muted-foreground">
- <span>Service: {trace.service_name || "Unknown"}</span>
- <span>Spans: {trace.span_count}</span>
+ <span>{t("pages.tracing.service")}: {trace.service_name || "Unknown"}</span>
+ <span>{t("pages.tracing.spans")}: {trace.span_count}</span>
<span>
</file context>
| requests_session = SessionLocal() # 创建实际的session实例 | ||
| queries_session = SessionLocal() # 创建实际的session实例 | ||
| exceptions_session = SessionLocal() # 创建实际的session实例 |
There was a problem hiding this comment.
Why create 3 database sessions?
There was a problem hiding this comment.
I don't want this either... Since duckdb does not support multiple begin, in order to use the sqlalchemy unified interface, I have no choice but to adopt this solution... Do you have any good methods? I really have no other good solutions for this place. Either I give up duckdb and just use sqlite directly
There was a problem hiding this comment.
The monitoring part was very simple, but I spent three days dealing with the part that had the 'begin' conflict, and ultimately chose this solution.
There was a problem hiding this comment.
I don't think the issue you mention applies in this case, as these are all SELECT queries within the same session, so nothing is being commit.
There was a problem hiding this comment.
You can give this part a try. It might actually be my fault, but I really don't have a better solution for it.
There was a problem hiding this comment.
我认为您提到的问题不适用于这种情况,因为这些都是
SELECT同一会话内的查询,因此没有提交任何内容。
As long as "execute" is performed, it means that "begin" has been activated. I saw someone mention this issue in the issues section of DuckDB. Perhaps it's because of the "with" dependency injection that automatically manages the lifecycle.
There was a problem hiding this comment.
Might that be related to the problem I reported? #45
The error (see details) looks like it would point in the general "no multiple '.begin' allowed" direction.
Summary
This PR enhances the tracing system with three major improvements:
Changes
1. HTTP Client Instrumentation
Added automatic instrumentation for popular Python HTTP clients:
Each HTTP request now automatically creates a span with:
Example span tags:
{ "component": "http", "http.method": "GET", "http.url": "https://api.example.com/users", "http.host": "api.example.com", "http.status_code": 200, "http.response_content_length": "1234" } 2. Tracing UI Localization (i18n) Complete internationalization support for the TracesList component: Translated elements: - Search placeholder: "Search by operation name..." / "按操作名称搜索..." - Status filters: "All statuses", "Success", "Error" / "所有状态", "成功", "错误" - Time ranges: "Last hour", "Last 6 hours", etc. / "最近1小时", "最近6小时" - Error messages and empty states - Table headers: "Service", "Spans", "Duration" / "服务", "Spans", "持续时间" 3. Search Functionality Added search capability to filter traces by operation name: Backend changes: - Added search query parameter to GET /api/traces - Filter traces using SQL ILIKE for case-insensitive matching - Example: /api/traces?search=HTTP%20GET Frontend changes: - Wire search input to API calls in TracesList.tsx - Update TypeScript client interface with search parameter - Real-time filtering as user types Technical Implementation Session Management Fix - Refactored get_waterfall_data to use session factory pattern - Fixes DuckDB transaction conflicts in dev mode - Separate session instances for parallel queries in /stats endpoint Code Quality - Improved error handling in trace detail endpoints - Better logging for debugging trace queries Testing Tested with: - Manual testing of HTTP instrumentation with httpx and requests - UI testing with Chinese and English locales - Search filtering with various operation names - Multiple concurrent trace requests Screenshots Add screenshots of the localized UI and search functionality if desired Breaking Changes None. This is a backward-compatible enhancement. ## Summary by Sourcery Enhance tracing by adding automatic outbound HTTP request instrumentation, UI localization, and operation-name search, while refactoring session management and improving error handling and logging in trace endpoints. New Features: - Automatically instrument outbound HTTP requests for requests, httpx, and aiohttp clients - Localize tracing UI with English and Chinese translations for filters, placeholders, and labels - Add search filter for operation names in the traces API and UI Enhancements: - Refactor get_waterfall_data to use session factory pattern and isolate sessions to prevent DuckDB transaction conflicts - Improve error handling and logging in trace detail and waterfall endpoints - Initialize HTTP client instrumentation during radar startup - Extend TypeScript API client to support the new search parameter Tests: - Add smoke tests triggering HTTP client requests via httpx and requests to verify instrumentation