Skip to content

Commit cb0b2fb

Browse files
📈 Add telemetry infrastructure, events and documentation (#22)
1 parent 33fc251 commit cb0b2fb

15 files changed

Lines changed: 859 additions & 34 deletions

File tree

‎.gitignore‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ _.log
2525

2626
/.agents/
2727
/.claude/
28-
CLAUDE.local.md
28+
CLAUDE.local.md
29+
30+
.env

‎README.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@ CodeLens links appear above HTTP client calls like `client.get('/items')`, letti
2929
| Setting | Description | Default |
3030
|---------|-------------|---------|
3131
| `fastapi.entryPoint` | Path to the main FastAPI application file (e.g., `src/main.py`). If not set, the extension searches common locations: `main.py`, `app/main.py`, `api/main.py`, `src/main.py`, `backend/app/main.py`. | `""` (auto-detect) |
32-
| `fastapi.showTestCodeLenses` | Show CodeLens links above test client calls (e.g., `client.get('/items')`) to navigate to the corresponding route definition. | `true` |
32+
| `fastapi.codeLens.enabled` | Show CodeLens links above test client calls (e.g., `client.get('/items')`) to navigate to the corresponding route definition. | `true` |
33+
| `fastapi.telemetry.enabled` | Send anonymous usage data to help improve the extension. See [TELEMETRY.md](TELEMETRY.md) for details on what is collected. | `true` |
3334

3435
**Note:** Currently the extension discovers one FastAPI app per workspace folder. If you have multiple apps, use separate workspace folders or configure `fastapi.entryPoint` to point to your primary app.
3536

37+
## Data and telemetry
38+
39+
The FastAPI extension collects anonymous usage data and sends it to FastAPI to help improve the extension. You can disable telemetry by setting `fastapi.telemetry.enabled` to `false`. Read our [TELEMETRY.md](TELEMETRY.md) for details on what we collect and what we don't.
40+
3641
## License
42+
MIT
3743

38-
MIT

‎TELEMETRY.md‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Telemetry
2+
3+
The FastAPI VS Code extension collects anonymous usage data to help us understand how the extension is used and how we can improve it. This document describes exactly what data is collected. No personally identifiable information is collected. No information is shared with third parties.
4+
5+
## How to disable telemetry
6+
7+
You can disable telemetry in two ways:
8+
9+
### Option 1: Disable all VS Code telemetry
10+
11+
1. Open VS Code Settings (File > Preferences > Settings, or `Cmd+,` on macOS)
12+
2. Search for `telemetry.telemetryLevel`
13+
3. Set it to `off`
14+
15+
This disables telemetry for VS Code and all extensions that respect this setting, including FastAPI.
16+
17+
### Option 2: Disable only FastAPI telemetry
18+
19+
1. Open VS Code Settings
20+
2. Search for `fastapi.telemetry.enabled`
21+
3. Uncheck the box (set to `false`)
22+
23+
This disables only the FastAPI extension's telemetry while leaving other telemetry unchanged.
24+
25+
**Note:** Telemetry is only sent when *both* VS Code's global telemetry (`telemetry.telemetryLevel`) is enabled *and* the extension setting (`fastapi.telemetry.enabled`) is `true`. Disabling either one will stop all telemetry collection.
26+
27+
## What we collect
28+
29+
We collect anonymous usage metrics to improve the extension. We do **not** collect:
30+
- File paths or file contents
31+
- Route paths or endpoint names
32+
- Any code from your project
33+
- IP addresses (geo-IP is disabled)
34+
35+
**Note:** All events include contextual information: client type (VS Code, Cursor, etc.), OS platform, CPU architecture, extension version, and if available, the installed Python version and versions of related packages (FastAPI, Pydantic, Starlette, Typer, FastAPI CLI, FastAPI Cloud CLI) from your active interpreter.
36+
37+
### Events
38+
39+
| Event | Data | Why |
40+
|-------|------|-----|
41+
| Extension activated | Activation duration, success/failure, number of routes/routers/apps discovered, workspace folder count | Helps us understand startup performance, project sizes, and environment compatibility |
42+
| Extension deactivated | Session duration (time from activation to deactivation) | Helps us understand how long users keep VS Code open with the extension active |
43+
| Activation failed | Error category (e.g., "parse_error", "wasm_load_error"), failure stage | Helps us debug issues users encounter |
44+
| Entrypoint detected | Detection duration, method used (config/pyproject/heuristic), success/failure, routes and routers count | Helps us understand which detection methods work best |
45+
| Tree view visible | _(none)_ | Know if users see the endpoint explorer |
46+
| Search executed | Number of results, whether user selected a result | Helps us understand search usage |
47+
| CodeLens provided | Number of test calls found, number matched to routes | Helps us understand CodeLens effectiveness |
48+
| Routes navigated | Count of navigations (cumulative) | Helps us understand feature usage depth |
49+
| Routes copied | Count of copies (cumulative) | Helps us understand feature usage depth |
50+
| CodeLens clicked | Count of clicks (cumulative) | Helps us understand feature usage depth |
51+
52+
### Identifiers
53+
54+
- A random UUID is generated and stored locally in VS Code's extension storage
55+
- This ID is used solely to count unique users and is not linked to any personal information
56+
57+
## Source code
58+
59+
The telemetry implementation is fully open source. See:
60+
- [src/utils/telemetry/](src/utils/telemetry/) - All telemetry code
61+
- [src/utils/telemetry/events.ts](src/utils/telemetry/events.ts) - Event definitions

‎bun.lock‎

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎esbuild.js‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import esbuild from "esbuild"
55
const production = process.argv.includes("--production")
66
const watch = process.argv.includes("--watch")
77

8+
const POSTHOG_API_KEY = "phc_s0Qx8NxueJvnqe4YE7NEKYNosJr8aZ81tIByuzm464X"
9+
810
function copyWasmFiles() {
911
const wasmDestDir = path.join(import.meta.dirname, "dist", "wasm")
1012
mkdirSync(wasmDestDir, { recursive: true })
@@ -44,6 +46,9 @@ async function main() {
4446
logLevel: "info",
4547
define: {
4648
"process.env.NODE_ENV": production ? '"production"' : '"development"',
49+
"process.env.POSTHOG_API_KEY": production
50+
? JSON.stringify(POSTHOG_API_KEY)
51+
: '""',
4752
__DIST_ROOT__: JSON.stringify(path.join(import.meta.dirname, "dist")),
4853
},
4954
}
@@ -74,7 +79,16 @@ async function main() {
7479
},
7580
// vscode is provided by the runtime; web-tree-sitter is bundled but
7681
// internally references these Node.js modules for environment detection
77-
external: ["vscode", "fs/promises", "module"],
82+
// posthog-node uses Node.js APIs, so telemetry is disabled in browser
83+
// util and child_process are used for version detection but not in browser
84+
external: [
85+
"vscode",
86+
"fs/promises",
87+
"module",
88+
"posthog-node",
89+
"util",
90+
"child_process",
91+
],
7892
})
7993

8094
if (watch) {

‎package.json‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,17 @@
203203
"scope": "resource",
204204
"description": "Path to the main FastAPI application file (e.g., 'src/main.py'). If not set, the extension will search common locations."
205205
},
206-
"fastapi.showTestCodeLenses": {
206+
"fastapi.codeLens.enabled": {
207207
"type": "boolean",
208208
"default": true,
209209
"scope": "resource",
210210
"description": "Show CodeLens links above test client calls (e.g., client.get('/items')) to navigate to the corresponding route definition."
211+
},
212+
"fastapi.telemetry.enabled": {
213+
"type": "boolean",
214+
"default": true,
215+
"scope": "machine",
216+
"description": "Enable telemetry to help improve the extension. No personal data is collected."
211217
}
212218
}
213219
}
@@ -238,6 +244,7 @@
238244
"typescript": "^5.0.0"
239245
},
240246
"dependencies": {
247+
"posthog-node": "^5.24.1",
241248
"toml": "^3.0.0",
242249
"web-tree-sitter": "^0.26.3"
243250
},

‎src/appDiscovery.ts‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ import { routerNodeToAppDefinition } from "./core/transformer"
1313
import type { AppDefinition } from "./core/types"
1414
import { vscodeFileSystem } from "./providers/vscodeFileSystem"
1515
import { log } from "./utils/logger"
16+
import {
17+
countRouters,
18+
countRoutes,
19+
createTimer,
20+
trackEntrypointDetected,
21+
} from "./utils/telemetry"
1622

1723
export type { EntryPoint }
1824

@@ -105,6 +111,9 @@ export async function discoverFastAPIApps(
105111
const apps: AppDefinition[] = []
106112

107113
for (const folder of workspaceFolders) {
114+
const folderTimer = createTimer()
115+
let detectionMethod: "config" | "pyproject" | "heuristic" = "heuristic"
116+
const folderApps: AppDefinition[] = []
108117
const config = vscode.workspace.getConfiguration("fastapi", folder.uri)
109118
const customEntryPoint = config.get<string>("entryPoint")
110119

@@ -126,14 +135,17 @@ export async function discoverFastAPIApps(
126135

127136
log(`Using custom entry point: ${customEntryPoint}`)
128137
candidates = [{ filePath: entryUri.toString() }]
138+
detectionMethod = "config"
129139
} else {
130140
// Otherwise, check pyproject.toml or auto-detect
131141
const pyprojectEntry = await parsePyprojectForEntryPoint(folder.uri)
132142
if (pyprojectEntry) {
133143
candidates = [pyprojectEntry]
144+
detectionMethod = "pyproject"
134145
} else {
135146
const detected = await automaticDetectEntryPoints(folder)
136147
candidates = detected.map((filePath) => ({ filePath }))
148+
detectionMethod = "heuristic"
137149
log(
138150
`Found ${candidates.length} candidate entry file(s) in ${folder.name}`,
139151
)
@@ -174,10 +186,20 @@ export async function discoverFastAPIApps(
174186
log(
175187
`Found FastAPI app "${app.name}" with ${totalRoutes} route(s) in ${app.routers.length} router(s)`,
176188
)
189+
folderApps.push(app)
177190
apps.push(app)
178-
break
191+
break // TODO: Only use first successful app per workspace folder, for now
179192
}
180193
}
194+
195+
// Track entrypoint detection per workspace folder
196+
trackEntrypointDetected({
197+
duration_ms: folderTimer(),
198+
method: detectionMethod,
199+
success: folderApps.length > 0,
200+
routes_count: countRoutes(folderApps),
201+
routers_count: countRouters(folderApps),
202+
})
181203
}
182204

183205
if (apps.length === 0) {

0 commit comments

Comments
 (0)