AI Trace Viewer now automatically tracks the source file location (file path and line number) for every log entry. This enables powerful IDE integration features like:
- Gutter marks showing which lines have trace data
- Clickable CodeLens to view trace details
- Jump to code from trace viewer
- VSCode/Cursor plugin integration
When you call setup_logging(), a special processor is added that:
- Captures the call site - Uses Python's
inspect.stack()to find where the log was called - Converts to relative path - Makes paths relative to your workspace root (detected automatically)
- Adds to log record - Injects
file,line, andfunctionfields into every log entry
{
"timestamp": "2025-10-25T10:30:15.123456Z",
"event": "user_login",
"level": "info",
"file": "src/auth/login.py",
"line": 42,
"function": "handle_login",
"trace_id": "abc123...",
"span_id": "def456...",
"user_id": 12345
}The system automatically detects your project root by looking for common markers (in priority order):
.gitdirectorypyproject.tomlsetup.pyrequirements.txt
If none are found, it uses the current working directory.
You can manually set the workspace root if automatic detection doesn't work:
from aitrace import logging_config
# Set workspace root explicitly
logging_config.set_workspace_root("/path/to/my/project")
# Check current workspace root
root = logging_config.get_workspace_root()
print(f"Workspace root: {root}")The generated trace files are compatible with the VSCode/Cursor plugin described in the specification.
The plugin expects NDJSON (newline-delimited JSON) files with these fields:
{
id: string; // trace_id or span_id
file: string; // relative path from workspace root
line: number; // 1-based line number
severity?: "info" | "warn" | "error";
label?: string; // event or custom label
payload?: any; // full log record
}AI Trace logs map to the plugin format as:
| AI Trace Field | Plugin Field | Notes |
|---|---|---|
span_id |
id |
Unique identifier |
file |
file |
Relative path ✓ |
line |
line |
Line number ✓ |
level |
severity |
info/warn/error |
event |
label |
Event name |
| (entire log) | payload |
Full log record |
To export traces in a format the plugin can consume:
from aitrace import BufferedLogger
# Option 1: Write to NDJSON file
buffered = BufferedLogger(target="~/traces/my_app.jsonl")
# ... your code with logging ...
buffered.flush()
# Option 2: Transform existing logs
import json
# Read from SQLite or existing logs
logs = get_all_logs() # your fetch method
# Write in plugin format
with open("trace_for_plugin.ndjson", "w") as f:
for log in logs:
plugin_record = {
"id": log["span_id"],
"file": log["file"], # already relative!
"line": log["line"],
"severity": log["level"],
"label": log.get("event", "log"),
"payload": log,
}
f.write(json.dumps(plugin_record) + "\n")Relative paths make traces portable across:
- Different machines - No need to adjust absolute paths
- Team environments - Everyone can use the same trace files
- CI/CD pipelines - Works regardless of checkout location
- Docker containers - Paths remain valid inside containers
The VSCode/Cursor plugin will need to:
- Read the trace file (NDJSON format)
- Detect workspace root (same logic: look for .git, pyproject.toml, etc.)
- Resolve absolute paths by combining:
absolute_path = workspace_root / relative_path
Example plugin code:
// Detect workspace root in VSCode
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
// Load trace record
const traceRecord = JSON.parse(line);
// Resolve to absolute path
const absolutePath = path.join(workspaceRoot, traceRecord.file);
// Open file at line
vscode.workspace.openTextDocument(absolutePath).then((doc) => {
vscode.window.showTextDocument(doc, {
selection: new vscode.Range(
traceRecord.line - 1,
0,
traceRecord.line - 1,
0
),
});
});Run the test script to verify source location tracking:
uv run python test_source_location.pyExpected output includes:
- Detected workspace root
- JSON logs with
file,line, andfunctionfields - All file paths are relative to workspace root
Source location tracking uses inspect.stack() which has a small performance cost:
- Typical overhead: ~10-50 microseconds per log call
- Impact: Negligible for most applications
- When to disable: Only in extreme high-frequency logging scenarios
The workspace root is detected once and cached, so subsequent lookups are instant.
If your code is inside a virtual environment (e.g., .venv), the paths may include the venv directory. This is usually fine, but you can adjust by setting the workspace root manually.
Paths are resolved (following symlinks) before making them relative. This ensures consistency but may show unexpected paths if you use symlinks creatively.
Logs from third-party libraries will show paths to their installation location, not your workspace. The system filters out internal logging framework calls but doesn't filter library code.
from fastapi import FastAPI
from aitrace import setup_tracing, setup_logging, auto_span
app = FastAPI()
tracer = setup_tracing("my-api")
log = setup_logging()
@app.get("/users/{user_id}")
@auto_span()
async def get_user(user_id: int):
log.info("fetching_user", user_id=user_id)
# file: api/routes/users.py, line: 12
user = await db.fetch_user(user_id)
log.info("user_fetched", user_id=user_id, found=user is not None)
# file: api/routes/users.py, line: 16
return userimport argparse
from aitrace import setup_tracing, setup_logging, BufferedLogger
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--trace", help="Save trace to file")
args = parser.parse_args()
# Setup tracing
tracer = setup_tracing("cli-tool")
# Use BufferedLogger to save traces
if args.trace:
buffered = BufferedLogger(target=args.trace)
log = buffered.logger
with tracer.start_as_current_span("main"):
# Your code here
log.info("processing_start")
# ... work ...
log.info("processing_complete")
buffered.flush()
else:
log = setup_logging()
# ... regular logging ...
if __name__ == "__main__":
main()from aitrace import setup_tracing, setup_logging, auto_span
tracer = setup_tracing("background-worker")
log = setup_logging()
@auto_span()
def process_job(job_id: str):
log.info("job_start", job_id=job_id)
# file: workers/processor.py, line: 10
try:
result = do_work(job_id)
log.info("job_success", job_id=job_id, result=result)
# file: workers/processor.py, line: 14
except Exception as e:
log.error("job_failed", job_id=job_id, error=str(e))
# file: workers/processor.py, line: 17
raisePotential improvements for the plugin integration:
- Automatic trace export - CLI command to export logs in plugin format
- Source maps support - Handle transpiled/compiled code
- Multi-project support - Handle monorepos with multiple workspace roots
- Smart path resolution - Better handling of venv, docker, etc.
- Column tracking - Add column number for precise location
- README.md - Main documentation
- VSCode Plugin Format - VSCode plugin integration
- Configuration Guide - Server configuration
- Test Examples - Example scripts