diff --git a/.chronus/changes/copilot-fix-lsp-debug-message-issue-2026-1-6-5-49-13.md b/.chronus/changes/copilot-fix-lsp-debug-message-issue-2026-1-6-5-49-13.md new file mode 100644 index 00000000000..51dddb2892a --- /dev/null +++ b/.chronus/changes/copilot-fix-lsp-debug-message-issue-2026-1-6-5-49-13.md @@ -0,0 +1,8 @@ +--- +changeKind: internal +packages: + - "@typespec/compiler" + - typespec-vscode +--- + +Gate debug logs behind TYPESPEC_DEBUG environment variable to suppress spam messages during compilation \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index c2ab0f27883..49952259cee 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -136,9 +136,13 @@ // Set the telemetry key environment variable to use if you dont want to set it in package.json //"TYPESPEC_VSCODE_TELEMETRY_KEY": "{The instrumentation key of your Application Insights}", - //"ENABLE_SERVER_COMPILE_LOGGING": "true", - //"ENABLE_UPDATE_MANAGER_LOGGING": "true", - //"ENABLE_LM_LOGGING": "true", + // Enable debug logging for specific areas using TYPESPEC_DEBUG environment variable + // Examples: + // "TYPESPEC_DEBUG": "server.compile" - Enable server compilation debug logs + // "TYPESPEC_DEBUG": "lm" - Enable Language Model debug logs + // "TYPESPEC_DEBUG": "*" - Enable all debug logs + // "TYPESPEC_DEBUG": "server.compile,compile.config" - Enable multiple areas + //"TYPESPEC_DEBUG": "server.compile,update.manager,compile.config,lm", "TYPESPEC_SERVER_NODE_OPTIONS": "--nolazy --inspect-brk=4242", "TYPESPEC_DEVELOPMENT_MODE": "true" diff --git a/packages/compiler/src/server/compile-service.ts b/packages/compiler/src/server/compile-service.ts index 2c41249149e..03875d7139f 100644 --- a/packages/compiler/src/server/compile-service.ts +++ b/packages/compiler/src/server/compile-service.ts @@ -20,6 +20,7 @@ import { getLocationInYamlScript } from "../yaml/diagnostics.js"; import { parseYaml } from "../yaml/parser.js"; import { ClientConfigProvider } from "./client-config-provider.js"; import { serverOptions } from "./constants.js"; +import { debugLoggers } from "./debug.js"; import { resolveEntrypointFile } from "./entrypoint-resolver.js"; import { FileService } from "./file-service.js"; import { FileSystemCache } from "./file-system-cache.js"; @@ -90,6 +91,8 @@ export function createCompileService({ const eventListeners = new Map void | Promise>(); const compileManager = new ServerCompileManager(updateManager, compilerHost, log); let configFilePath: string | undefined; + const debug = debugLoggers.compileConfig; + const logDebug = debug.enabled ? log : () => {}; return { compile, getScript, on, notifyChange, getMainFileForDocument }; @@ -129,7 +132,7 @@ export function createCompileService({ } const mainFile = await getMainFileForDocument(path); if (mainFile === undefined) { - log({ level: "debug", message: `failed to resolve main file for ${path}` }); + logDebug({ level: "debug", message: `failed to resolve main file for ${path}` }); return undefined; } if (!mainFile.endsWith(".tsp")) { @@ -137,7 +140,7 @@ export function createCompileService({ } const config = await getConfig(mainFile); configFilePath = config.filename; - log({ level: "debug", message: `config resolved`, detail: config }); + logDebug({ level: "debug", message: `config resolved`, detail: config }); const [optionsFromConfig, _] = resolveOptionsFromConfig(config, { cwd: getDirectoryPath(path), }); @@ -217,7 +220,7 @@ export function createCompileService({ ) { // If the file that changed wasn't imported by anything from the main // file, retry using the file itself as the main file. - log({ + logDebug({ level: "debug", message: `target file was not included in compiling, try to compile ${path} as main file directly`, }); @@ -246,7 +249,7 @@ export function createCompileService({ const [yamlScript] = parseYaml(await serverHost.compilerHost.readFile(configFilePath)); const target = getLocationInYamlScript(yamlScript, ["emit", emitterName], "key"); if (target.pos === 0) { - log({ + logDebug({ level: "debug", message: `Unexpected situation, can't find emitter '${emitterName}' in config file '${configFilePath}'`, }); @@ -286,7 +289,7 @@ export function createCompileService({ const lookupDir = entrypointStat.isDirectory() ? mainFile : getDirectoryPath(mainFile); const configPath = await findTypeSpecConfigPath(compilerHost, lookupDir, true); if (!configPath) { - log({ + logDebug({ level: "debug", message: `can't find path with config file, try to use default config`, }); @@ -337,7 +340,10 @@ export function createCompileService({ */ async function getMainFileForDocument(path: string) { if (path.startsWith("untitled:")) { - log({ level: "debug", message: `untitled document treated as its own main file: ${path}` }); + logDebug({ + level: "debug", + message: `untitled document treated as its own main file: ${path}`, + }); return path; } diff --git a/packages/compiler/src/server/constants.ts b/packages/compiler/src/server/constants.ts index 1bb0333bd04..65ffde94c19 100644 --- a/packages/compiler/src/server/constants.ts +++ b/packages/compiler/src/server/constants.ts @@ -12,9 +12,3 @@ export const serverOptions: CompilerOptions = { export const Commands = { APPLY_CODE_FIX: "typespec.applyCodeFix", }; - -/** - * Environment variables to enable some logging when needed - */ -export const ENABLE_SERVER_COMPILE_LOGGING = "ENABLE_SERVER_COMPILE_LOGGING"; -export const ENABLE_UPDATE_MANAGER_LOGGING = "ENABLE_UPDATE_MANAGER_LOGGING"; diff --git a/packages/compiler/src/server/debug.ts b/packages/compiler/src/server/debug.ts new file mode 100644 index 00000000000..006afee6502 --- /dev/null +++ b/packages/compiler/src/server/debug.ts @@ -0,0 +1,55 @@ +import { getEnvironmentVariable } from "../utils/misc.js"; + +/** + * Debug areas that can be enabled via TYPESPEC_DEBUG environment variable. + * + * Note: We use TYPESPEC_DEBUG instead of DEBUG because the DEBUG environment variable + * is not supported in VSCode extensions. See: https://github.com/microsoft/vscode/issues/290140 + * + * Usage: TYPESPEC_DEBUG=server.compile,compile.config + * + * Examples: + * TYPESPEC_DEBUG=server.compile - Enable server compilation debug logs + * TYPESPEC_DEBUG=* - Enable all debug logs + * TYPESPEC_DEBUG=server.compile,compile.config - Enable multiple areas + */ +const debugAreas = { + serverCompile: "server.compile", + updateManager: "update.manager", + compileConfig: "compile.config", +} as const; + +/** + * Check if a debug area is enabled via the TYPESPEC_DEBUG environment variable. + * Supports comma-separated values and wildcards. + */ +function isDebugEnabled(area: string): boolean { + const debug = getEnvironmentVariable("TYPESPEC_DEBUG"); + if (!debug) { + return false; + } + + const areas = debug.split(",").map((a) => a.trim()); + + return areas.some((pattern) => { + // Exact match + if (pattern === area) { + return true; + } + + // Wildcard pattern matching + if (pattern.includes("*")) { + const regexPattern = pattern.replace(/\*/g, ".*"); + const regex = new RegExp(`^${regexPattern}$`); + return regex.test(area); + } + + return false; + }); +} + +export const debugLoggers = { + serverCompile: { enabled: isDebugEnabled(debugAreas.serverCompile) }, + updateManager: { enabled: isDebugEnabled(debugAreas.updateManager) }, + compileConfig: { enabled: isDebugEnabled(debugAreas.compileConfig) }, +} as const; diff --git a/packages/compiler/src/server/entrypoint-resolver.ts b/packages/compiler/src/server/entrypoint-resolver.ts index 4865b44d4c6..e60e82d74ce 100644 --- a/packages/compiler/src/server/entrypoint-resolver.ts +++ b/packages/compiler/src/server/entrypoint-resolver.ts @@ -3,6 +3,7 @@ import { getDirectoryPath, joinPaths } from "../core/path-utils.js"; import { SystemHost, Diagnostic as TypeSpecDiagnostic } from "../core/types.js"; import { doIO, loadFile } from "../utils/io.js"; import { resolveTspMain } from "../utils/misc.js"; +import { debugLoggers } from "./debug.js"; import { FileSystemCache } from "./file-system-cache.js"; import { ServerLog } from "./types.js"; @@ -14,6 +15,8 @@ export async function resolveEntrypointFile( log: (log: ServerLog) => void, ): Promise { const options = { allowFileNotFound: true }; + const debug = debugLoggers.compileConfig; + const logDebug = debug.enabled ? log : () => {}; const pathStat = await doIO(() => host.stat(path), path, logMainFileSearchDiagnostic, options); const isFilePath = pathStat?.isFile() ?? false; @@ -36,14 +39,14 @@ export async function resolveEntrypointFile( const tspMain = resolveTspMain(pkg); if (typeof tspMain === "string") { - log({ + logDebug({ level: "debug", message: `tspMain resolved from package.json (${pkgPath}) as ${tspMain}`, }); const packageJsonEntrypoint = await existingFile(dir, tspMain); if (packageJsonEntrypoint) { - log({ level: "debug", message: `entrypoint file found as ${packageJsonEntrypoint}` }); + logDebug({ level: "debug", message: `entrypoint file found as ${packageJsonEntrypoint}` }); return packageJsonEntrypoint; } } @@ -51,7 +54,7 @@ export async function resolveEntrypointFile( for (const entrypoint of entrypoints) { const candidate = await existingFile(dir, entrypoint); if (candidate) { - log({ + logDebug({ level: "debug", message: `main file found using client provided entrypoint: ${candidate}`, }); @@ -67,7 +70,7 @@ export async function resolveEntrypointFile( dir = parentDir; } - log({ level: "debug", message: `reached directory root, using '${path}' as main file` }); + logDebug({ level: "debug", message: `reached directory root, using '${path}' as main file` }); return isFilePath ? path : undefined; function logMainFileSearchDiagnostic(diagnostic: TypeSpecDiagnostic) { diff --git a/packages/compiler/src/server/server-compile-manager.ts b/packages/compiler/src/server/server-compile-manager.ts index 552c0551893..19cd7708b04 100644 --- a/packages/compiler/src/server/server-compile-manager.ts +++ b/packages/compiler/src/server/server-compile-manager.ts @@ -8,8 +8,7 @@ import { Program, ServerLog, } from "../index.js"; -import { getEnvironmentVariable } from "../utils/misc.js"; -import { ENABLE_SERVER_COMPILE_LOGGING } from "./constants.js"; +import { debugLoggers } from "./debug.js"; import { trackActionFunc } from "./server-track-action-task.js"; import { UpdateManager } from "./update-manager.js"; @@ -45,10 +44,8 @@ export class ServerCompileManager { private compilerHost: CompilerHost, private log: (log: ServerLog) => void, ) { - this.logDebug = - getEnvironmentVariable(ENABLE_SERVER_COMPILE_LOGGING)?.toLowerCase() === "true" - ? (msg) => this.log({ level: "debug", message: msg }) - : () => {}; + const debug = debugLoggers.serverCompile; + this.logDebug = debug.enabled ? (msg) => this.log({ level: "debug", message: msg }) : () => {}; } async compile( diff --git a/packages/compiler/src/server/update-manager.ts b/packages/compiler/src/server/update-manager.ts index 0d650dbc359..72e019c1b32 100644 --- a/packages/compiler/src/server/update-manager.ts +++ b/packages/compiler/src/server/update-manager.ts @@ -1,7 +1,6 @@ import { TextDocumentIdentifier } from "vscode-languageserver"; import { TextDocument } from "vscode-languageserver-textdocument"; -import { getEnvironmentVariable } from "../utils/misc.js"; -import { ENABLE_UPDATE_MANAGER_LOGGING } from "./constants.js"; +import { debugLoggers } from "./debug.js"; import { ServerLog } from "./types.js"; interface PendingUpdate { @@ -43,12 +42,12 @@ export class UpdateManager { log: (sl: ServerLog) => void, getDebounceDelay?: () => number, ) { - this._log = - getEnvironmentVariable(ENABLE_UPDATE_MANAGER_LOGGING)?.toLowerCase() === "true" - ? (sl: ServerLog) => { - log({ ...sl, message: `#FromUpdateManager(${this.name}): ${sl.message}` }); - } - : () => {}; + const debug = debugLoggers.updateManager; + this._log = debug.enabled + ? (sl: ServerLog) => { + log({ ...sl, message: `#FromUpdateManager(${this.name}): ${sl.message}` }); + } + : () => {}; // Set the debounce delay function once during construction this.getDebounceDelay = getDebounceDelay ?? this.getAdaptiveDebounceDelay; diff --git a/packages/typespec-vscode/src/const.ts b/packages/typespec-vscode/src/const.ts index 39df7d9279c..5e75dd8acfa 100644 --- a/packages/typespec-vscode/src/const.ts +++ b/packages/typespec-vscode/src/const.ts @@ -1,5 +1,3 @@ export const StartFileName = "main.tsp"; export const TspConfigFileName = "tspconfig.yaml"; export const EmptyGuid = "00000000-0000-0000-0000-000000000000"; - -export const ENABLE_LM_LOGGING = "ENABLE_LM_LOGGING"; diff --git a/packages/typespec-vscode/src/debug.ts b/packages/typespec-vscode/src/debug.ts new file mode 100644 index 00000000000..fd06e6edeca --- /dev/null +++ b/packages/typespec-vscode/src/debug.ts @@ -0,0 +1,41 @@ +/** + * Debug logger for Language Model operations. + * Can be enabled via TYPESPEC_DEBUG environment variable. + * + * Note: We use TYPESPEC_DEBUG instead of DEBUG because the DEBUG environment variable + * is not supported in VSCode extensions. See: https://github.com/microsoft/vscode/issues/290140 + * + * Usage: TYPESPEC_DEBUG=lm + * + * Examples: + * TYPESPEC_DEBUG=lm - Enable Language Model debug logs + * TYPESPEC_DEBUG=* - Enable all debug logs + */ +function isDebugEnabled(area: string): boolean { + const debug = process.env.TYPESPEC_DEBUG; + if (!debug) { + return false; + } + + const areas = debug.split(",").map((a) => a.trim()); + + return areas.some((pattern) => { + // Exact match + if (pattern === area) { + return true; + } + + // Wildcard pattern matching + if (pattern.includes("*")) { + const regexPattern = pattern.replace(/\*/g, ".*"); + const regex = new RegExp(`^${regexPattern}$`); + return regex.test(area); + } + + return false; + }); +} + +export const debugLoggers = { + lm: { enabled: isDebugEnabled("lm") }, +} as const; diff --git a/packages/typespec-vscode/src/lm/language-model.ts b/packages/typespec-vscode/src/lm/language-model.ts index 3dfb8d581d6..79ca770aff9 100644 --- a/packages/typespec-vscode/src/lm/language-model.ts +++ b/packages/typespec-vscode/src/lm/language-model.ts @@ -1,6 +1,6 @@ import { inspect } from "util"; import { LanguageModelChat, LanguageModelChatMessage, lm } from "vscode"; -import { ENABLE_LM_LOGGING } from "../const"; +import { debugLoggers } from "../debug"; import logger, { LogItem } from "../log/logger"; import { RetryResult, runWithRetry, runWithTimingLog } from "../utils"; @@ -23,7 +23,7 @@ export async function sendLmChatRequest( /** Only for logging purpose */ id?: string, ): Promise { - const logEnabled = process.env[ENABLE_LM_LOGGING] === "true"; + const logEnabled = debugLoggers.lm.enabled; const lmLog = (item: LogItem) => { if (logEnabled || item.level === "error" || item.level === "warning") { logger.log(