Skip to content

Commit db175fa

Browse files
Add output logging channel for debug logs
1 parent 08a9466 commit db175fa

4 files changed

Lines changed: 90 additions & 3 deletions

File tree

src/core/analyzer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { readFileSync } from "node:fs"
66
import type { Tree } from "web-tree-sitter"
7+
import { logError } from "../utils/logger"
78
import {
89
decoratorExtractor,
910
findNodesByType,
@@ -55,10 +56,12 @@ export function analyzeFile(
5556
const code = readFileSync(filePath, "utf-8")
5657
const tree = parser.parse(code)
5758
if (!tree) {
59+
logError(`Failed to parse file: "${filePath}"`)
5860
return null
5961
}
6062
return analyzeTree(tree, filePath)
61-
} catch {
63+
} catch (error) {
64+
logError(`Error reading file: "${filePath}"`, error)
6265
return null
6366
}
6467
}

src/core/routerResolver.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { existsSync } from "node:fs"
22
import { isAbsolute, join } from "node:path"
3+
import { log } from "../utils/logger"
34
import { analyzeFile } from "./analyzer"
45
import { resolveNamedImport, resolveRouterFromInit } from "./importResolver"
56
import type { FileAnalysis, RouterInfo, RouterNode } from "./internal"
@@ -46,10 +47,12 @@ function buildRouterGraphInternal(
4647
}
4748

4849
if (!existsSync(resolvedEntryFile)) {
50+
log(`File not found: "${entryFile}"`)
4951
return null
5052
}
5153
// Prevent infinite recursion on circular imports
5254
if (visited.has(resolvedEntryFile)) {
55+
log(`Skipping already visited file: "${resolvedEntryFile}"`)
5356
return null
5457
}
5558

@@ -58,9 +61,14 @@ function buildRouterGraphInternal(
5861
// Analyze the entry file
5962
let analysis = analyzeFile(resolvedEntryFile, parser)
6063
if (!analysis) {
64+
log(`Failed to analyze file: "${resolvedEntryFile}"`)
6165
return null
6266
}
6367

68+
log(
69+
`Analyzed "${resolvedEntryFile}": ${analysis.routes.length} routes, ${analysis.routers.length} routers, ${analysis.includeRouters.length} include_router calls`,
70+
)
71+
6472
// Find FastAPI instantiation
6573
let appRouter = findAppRouter(analysis.routers)
6674

@@ -114,6 +122,9 @@ function buildRouterGraphInternal(
114122

115123
// Process include_router calls to find child routers
116124
for (const include of analysis.includeRouters) {
125+
log(
126+
`Resolving include_router: ${include.router} (prefix: ${include.prefix || "none"})`,
127+
)
117128
const childRouter = resolveRouterReference(
118129
include.router,
119130
analysis,
@@ -204,6 +215,7 @@ function resolveRouterReference(
204215
)
205216

206217
if (!matchingImport) {
218+
log(`No import found for router reference: ${reference}`)
207219
return null
208220
}
209221

@@ -220,6 +232,7 @@ function resolveRouterReference(
220232
)
221233

222234
if (!importedFilePath) {
235+
log(`Could not resolve import: ${matchingImport.modulePath}`)
223236
return null
224237
}
225238

src/extension.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import { existsSync } from "node:fs"
6-
import { sep } from "node:path"
6+
import { basename, sep } from "node:path"
77
import * as vscode from "vscode"
88
import { clearImportCache } from "./core/importResolver"
99
import { Parser } from "./core/parser"
@@ -16,15 +16,21 @@ import {
1616
EndpointTreeProvider,
1717
} from "./providers/EndpointTreeProvider"
1818
import { TestCodeLensProvider } from "./providers/TestCodeLensProvider"
19+
import { disposeLogger, log } from "./utils/logger"
1920

2021
async function discoverFastAPIApps(parser: Parser): Promise<AppDefinition[]> {
2122
const apps: AppDefinition[] = []
2223
const workspaceFolders = vscode.workspace.workspaceFolders
2324

2425
if (!workspaceFolders) {
26+
log("No workspace folders found")
2527
return apps
2628
}
2729

30+
log(
31+
`Discovering FastAPI apps in ${workspaceFolders.length} workspace folder(s)...`,
32+
)
33+
2834
for (const folder of workspaceFolders) {
2935
const config = vscode.workspace.getConfiguration("fastapi", folder.uri)
3036
const customEntryPoint = config.get<string>("entryPoint")
@@ -38,12 +44,14 @@ async function discoverFastAPIApps(parser: Parser): Promise<AppDefinition[]> {
3844
: vscode.Uri.joinPath(folder.uri, customEntryPoint).fsPath
3945

4046
if (!existsSync(entryPath)) {
47+
log(`Custom entry point not found: ${customEntryPoint}`)
4148
vscode.window.showWarningMessage(
4249
`FastAPI entry point not found: ${customEntryPoint}`,
4350
)
4451
continue
4552
}
4653

54+
log(`Using custom entry point: ${customEntryPoint}`)
4755
candidates = [entryPath]
4856
} else {
4957
// Scan for main.py and __init__.py files (likely FastAPI entry points)
@@ -57,19 +65,37 @@ async function discoverFastAPIApps(parser: Parser): Promise<AppDefinition[]> {
5765
candidates = [...mainFiles, ...initFiles]
5866
.map((uri) => uri.fsPath)
5967
.sort((a, b) => a.split(sep).length - b.split(sep).length)
68+
log(
69+
`Found ${candidates.length} candidate entry file(s) in ${basename(folder.uri.fsPath)}`,
70+
)
6071
}
6172

6273
for (const entryPath of candidates) {
6374
const projectRoot = findProjectRoot(entryPath, folder.uri.fsPath)
6475
const routerNode = buildRouterGraph(entryPath, parser, projectRoot)
6576

6677
if (routerNode) {
67-
apps.push(routerNodeToAppDefinition(routerNode, folder.uri.fsPath))
78+
const app = routerNodeToAppDefinition(routerNode, folder.uri.fsPath)
79+
apps.push(app)
80+
// Count all routes: direct routes + routes in all routers (recursively)
81+
const countRoutes = (routers: typeof app.routers): number =>
82+
routers.reduce(
83+
(sum, r) => sum + r.routes.length + countRoutes(r.children),
84+
0,
85+
)
86+
const totalRoutes = app.routes.length + countRoutes(app.routers)
87+
log(
88+
`Found FastAPI app "${app.name}" with ${totalRoutes} route(s) in ${app.routers.length} router(s)`,
89+
)
6890
break
6991
}
7092
}
7193
}
7294

95+
if (apps.length === 0) {
96+
log("No FastAPI apps found in workspace")
97+
}
98+
7399
return apps
74100
}
75101

@@ -84,6 +110,13 @@ function navigateToLocation(location: SourceLocation): void {
84110
let parserService: Parser | null = null
85111

86112
export async function activate(context: vscode.ExtensionContext) {
113+
const extensionVersion =
114+
vscode.extensions.getExtension("FastAPILabs.fastapi-vscode")?.packageJSON
115+
?.version ?? "unknown"
116+
log(
117+
`FastAPI extension ${extensionVersion} activated (VS Code ${vscode.version})`,
118+
)
119+
87120
parserService = new Parser()
88121
await parserService.init({
89122
core: vscode.Uri.joinPath(
@@ -223,7 +256,9 @@ export async function activate(context: vscode.ExtensionContext) {
223256
}
224257

225258
export function deactivate() {
259+
log("Extension deactivated")
226260
parserService?.dispose()
227261
parserService = null
228262
clearImportCache()
263+
disposeLogger()
229264
}

src/utils/logger.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Output channel logger for the FastAPI extension.
3+
* Provides visibility into extension activity for troubleshooting.
4+
*
5+
* Uses LogOutputChannel for colored log levels and automatic timestamps.
6+
*/
7+
8+
import * as vscode from "vscode"
9+
10+
let outputChannel: vscode.LogOutputChannel | null = null
11+
12+
function getOutputChannel(): vscode.LogOutputChannel {
13+
if (!outputChannel) {
14+
outputChannel = vscode.window.createOutputChannel("FastAPI", { log: true })
15+
}
16+
return outputChannel
17+
}
18+
19+
export function log(message: string): void {
20+
getOutputChannel().info(message)
21+
}
22+
23+
export function logError(message: string, error?: unknown): void {
24+
if (error instanceof Error) {
25+
getOutputChannel().error(`${message}: ${error.message}`)
26+
} else if (error !== undefined) {
27+
getOutputChannel().error(`${message}: ${String(error)}`)
28+
} else {
29+
getOutputChannel().error(message)
30+
}
31+
}
32+
33+
export function disposeLogger(): void {
34+
outputChannel?.dispose()
35+
outputChannel = null
36+
}

0 commit comments

Comments
 (0)