diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3941037fc7..15bedd6eb2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -43,6 +43,7 @@ - Use `test/*/integrationTests/integrationHelpers.ts` for test setup utilities - Tests use Jest with VS Code test environment and require workspace test assets - Run with `npm run test:integration:*` commands (e.g., `npm run test:integration:csharp`) +- **Comments**: Prefer why comments over what comments. For complex logic, include references to related files or documentation. Do not remove existing comments unless they are clearly outdated or incorrect. ## Integration Points - **GitHub Copilot**: Extension registers C# context and related files providers if Copilot/Copilot Chat extensions are present. diff --git a/SUPPORT.md b/SUPPORT.md index 8a34736472..f5753dc903 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -18,23 +18,34 @@ We highly recommend using the C# extension's built-in command, `CSharp: Report a #### Capturing activity trace logging -When investigating issues, the C# extension provides a command to capture trace-level logs while you reproduce the issue. This is the recommended way to collect logs as it automatically captures all relevant log files in a single archive. +When investigating issues, the C# extension provides a command to collect logs, activity logs, traces, and dumps. This is the recommended way to collect diagnostic information as it automatically captures all relevant files in a single archive. -1. **Invoke the Capture Logs Command**: +1. **Invoke the Collect Logs Command**: - Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS). - - Search for and select `CSharp: Capture Logs` (`csharp.captureLogs`). -![alt text](docs/images/captureLogsCommand.png) -2. **Reproduce the Issue**: - - A notification will appear indicating that logs are being captured. + - Search for and select `CSharp: Collect C# Logs` (`csharp.collectLogs`). + + ![alt text](docs/images/collect_logs.png) +2. **Select Additional Logs to Collect**: + - You will be presented with a multi-select picker. Choose from: + - **Record Activity** - Capture live C#, LSP trace, and Razor log output + - **Performance Trace** - Record a dotnet-trace of the language server + - **Memory Dump** - Process memory dump using dotnet-dump + - **GC Dump** - Garbage collector heap dump using dotnet-gcdump + + ![alt text](docs/images/choose_additional_logs.png) +3. **Customize Arguments**: + - For each selected tool (dotnet-trace, dotnet-dump, dotnet-gcdump), you will be prompted to review and customize the arguments. +4. **Choose Where to Save**: + - You will be prompted to choose a save location for the log archive. Choose a location to save the `.zip` file. +4. **Reproduce the Issue**: + - If you selected Record Activity or Performance Trace, a notification will appear indicating that recording is in progress. - While the notification is visible, perform the actions that reproduce the issue. - The extension automatically sets the log level to `Trace` during capture to collect detailed information. - -3. **Stop Capturing and Save**: - - Click the `Cancel` button on the notification to stop capturing. - - You will be prompted to save the log archive. Choose a location to save the `.zip` file. - -4. **Share the Logs**: - - The saved archive contains: + - Click the `Cancel` button on the notification to stop recording. +5. **Zip File is Saved**: + - You will be notified that the archive has been saved and a button is provided to open the containing folder. +6. **Share the Logs**: + - The saved archive may contain (depending on selections): - `csharp.log` - The existing C# log file - `csharp-lsp-trace.log` - The existing LSP trace log file - `razor.log` - The existing Razor log file @@ -42,6 +53,9 @@ When investigating issues, the C# extension provides a command to capture trace- - `csharp-lsp-trace.activity.log` - Captured LSP trace activity during the recording session - `razor.activity.log` - Captured Razor log activity during the recording session - `csharp-settings.json` - Current C# extension settings + - `.nettrace` file from dotnet-trace (if Performance Trace selected) + - `.dmp` memory dump files (if Memory Dump selected) + - `.gcdump` GC dump files (if GC Dump selected) - Attach the archive to your GitHub issue or share it privately (see [Sharing information privately](#sharing-information-privately)). > [!WARNING] @@ -128,64 +142,6 @@ If the language server crashes, general logs are often helpful for diagnosing th > [!WARNING] > The dump will contain detailed information about the workspace. See [Sharing information privately](#sharing-information-privately) -### Recording a language server trace - -When investigating performance issues, we may request a performance trace of the language server to diagnose what is causing the problem. These are typically taken via [dotnet-trace](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-trace) (a cross platform tool to collect performance traces of .NET processes) - -The C# extension has a built in command, `csharp.recordLanguageServerTrace` to help with trace collection. This command will install `dotnet-trace` as a global tool, invoke it against the language server, and package the results along with logs into a `.zip` archive. - -1. Invoke the record language server trace command. -![alt text](docs/images/recordTraceCommand.png) -2. Accept the default trace arguments, or change them if requested. -![alt text](docs/images/dotnetTraceArguments.png) -3. Optionally select dump(s) to capture before and after the trace (Memory Dump and/or GC Dump). This is useful for comparing memory state before and after the trace. -![alt text](docs/images/selectDumpsWithTrace.png) -4. If any dumps are selected, you will be prompted to customize the dump arguments. -![alt text](docs/images/dotnetDumpArguments.png) -![alt text](docs/images/dotnetGcDumpArguments.png) -5. Choose a location to save the trace archive (`.zip` file). -6. A new terminal window will open to run the trace collection. While the trace is running, reproduce the performance issue. When done, hit or in the trace window to stop the trace, or click `Cancel` on the progress notification. -![alt text](docs/images/recordTraceTerminal.png) -7. The extension will automatically package the trace, logs, and any dumps into an archive. -8. Attach the archive to your GitHub issue or share it privately (see [Sharing information privately](#sharing-information-privately)). - -The saved archive contains: -- The `.nettrace` file from dotnet-trace -- `csharp.log` - The existing C# log file -- `csharp-lsp-trace.log` - The existing LSP trace log file -- `razor.log` - The existing Razor log file -- `csharp.activity.log` - Captured C# log activity during the trace session -- `csharp-lsp-trace.activity.log` - Captured LSP trace activity during the trace session -- `razor.activity.log` - Captured Razor log activity during the trace session -- `csharp-settings.json` - Current C# extension settings -- Any memory dumps (`.dmp`) or GC dumps (`.gcdump`) captured before/after the trace - -> [!WARNING] -> The trace and logs will contain detailed information about the workspace. See [Sharing information privately](#sharing-information-privately) - -### Collecting a dump - -When investigating memory issues or hangs, we may request a dump of the language server. There are two types of dumps available: - -- **Memory Dump**: A full process memory dump collected via [dotnet-dump](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-dump). Use this for investigating hangs or detailed memory analysis. -- **GC Dump**: A garbage collector heap dump collected via [dotnet-gcdump](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/dotnet-gcdump). These are smaller than full memory dumps and focus on managed heap information. Use this for investigating memory leaks or object retention issues. - -The C# extension has a built in command, `csharp.collectDump` to help with dump collection. This command will install the necessary dotnet tools, invoke them against the language server, and package the result into a `.zip` archive. - -1. Invoke the collect dump command by opening the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P` on macOS) and selecting `CSharp: Collect a dump of the C# Language Server`. -![alt text](docs/images/collectDumpCommand.png) -2. Select which dumps to collect (Memory Dump and/or GC Dump). The archive will also include current C# extension log files and settings. -![alt text](docs/images/selectDumpTypes.png) -3. You will be prompted to customize the dump arguments. -![alt text](docs/images/dotnetDumpArguments.png) -![alt text](docs/images/dotnetGcDumpArguments.png) -4. Choose a location to save the archive (`.zip` file). -5. The extension will collect the selected content and package it into an archive. -6. Attach the archive to your GitHub issue or share it privately (see [Sharing information privately](#sharing-information-privately)). - -> [!WARNING] -> Dumps will contain detailed information about the workspace, including file contents loaded in memory. See [Sharing information privately](#sharing-information-privately) - ### Sharing information privately Detailed logs, dumps, traces, and other information can sometimes contain private information that you do not wish to share publicly on GitHub (for example file paths and file contents). Instead, you can utilize the Developer Community page to share these privately to Microsoft. diff --git a/docs/images/captureLogsCommand.png b/docs/images/captureLogsCommand.png deleted file mode 100644 index 5a8f70e519..0000000000 Binary files a/docs/images/captureLogsCommand.png and /dev/null differ diff --git a/docs/images/choose_additional_logs.png b/docs/images/choose_additional_logs.png new file mode 100644 index 0000000000..95223f6f62 Binary files /dev/null and b/docs/images/choose_additional_logs.png differ diff --git a/docs/images/collectDumpCommand.png b/docs/images/collectDumpCommand.png deleted file mode 100644 index 7ae530611c..0000000000 Binary files a/docs/images/collectDumpCommand.png and /dev/null differ diff --git a/docs/images/collect_logs.png b/docs/images/collect_logs.png new file mode 100644 index 0000000000..60e73470c5 Binary files /dev/null and b/docs/images/collect_logs.png differ diff --git a/docs/images/recordTraceCommand.png b/docs/images/recordTraceCommand.png deleted file mode 100644 index 51c1e8b6fd..0000000000 Binary files a/docs/images/recordTraceCommand.png and /dev/null differ diff --git a/docs/images/selectDumpTypes.png b/docs/images/selectDumpTypes.png deleted file mode 100644 index c8ac8649eb..0000000000 Binary files a/docs/images/selectDumpTypes.png and /dev/null differ diff --git a/docs/images/selectDumpsWithTrace.png b/docs/images/selectDumpsWithTrace.png deleted file mode 100644 index 82eba7f0c1..0000000000 Binary files a/docs/images/selectDumpsWithTrace.png and /dev/null differ diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index f0e2bc952d..4b8927e7f6 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -171,33 +171,6 @@ "Failed to change context for {0}: {1}": "Failed to change context for {0}: {1}", "Select project context": "Select project context", "C# configuration has changed. Would you like to reload the window to apply your changes?": "C# configuration has changed. Would you like to reload the window to apply your changes?", - "Language server process not found, ensure the server is running.": "Language server process not found, ensure the server is running.", - "Capture Dumps With Trace": "Capture Dumps With Trace", - "Optionally select dump(s) to capture before and after the trace": "Optionally select dump(s) to capture before and after the trace", - "Save Trace and Logs": "Save Trace and Logs", - "Folder for trace file {0} does not exist/{0} is the folder path": { - "message": "Folder for trace file {0} does not exist", - "comment": [ - "{0} is the folder path" - ] - }, - "Failed to execute dotnet-trace command: {0}/dotnet-trace is a command name and should not be localized": { - "message": "Failed to execute dotnet-trace command: {0}", - "comment": [ - "dotnet-trace is a command name and should not be localized" - ] - }, - "Open Folder": "Open Folder", - "C# logs saved successfully.": "C# logs saved successfully.", - "Verifying dotnet-trace.../dotnet-trace is a command name and should not be localized": { - "message": "Verifying dotnet-trace...", - "comment": [ - "dotnet-trace is a command name and should not be localized" - ] - }, - "Required dump tools could not be installed.": "Required dump tools could not be installed.", - "Recording logs... Click Cancel to stop and save.": "Recording logs... Click Cancel to stop and save.", - "Creating archive...": "Creating archive...", "Collecting memory dump...": "Collecting memory dump...", "Collecting GC dump...": "Collecting GC dump...", "Memory Dump": "Memory Dump", @@ -217,52 +190,36 @@ ] }, "You can modify the default arguments if needed": "You can modify the default arguments if needed", - "Install": "Install", - "{0} not found, run \"{1}\" to install it?/{0} is the tool name and should not be localized{1} is the install command": { - "message": "{0} not found, run \"{1}\" to install it?", - "comment": [ - "{0} is the tool name and should not be localized", - "{1} is the install command" - ] - }, - "Installing {0}.../{0} is the tool name and should not be localized": { - "message": "Installing {0}...", - "comment": [ - "{0} is the tool name and should not be localized" - ] - }, - "Failed to install {0}, it may need to be manually installed. See C# output for details./{0} is the tool name and should not be localized": { - "message": "Failed to install {0}, it may need to be manually installed. See C# output for details.", - "comment": [ - "{0} is the tool name and should not be localized" - ] - }, - "Select Dump Type": "Select Dump Type", - "Choose dump type(s) to collect": "Choose dump type(s) to collect", - "Save Dump": "Save Dump", - "Folder for dump file {0} does not exist/{0} is the folder path": { - "message": "Folder for dump file {0} does not exist", - "comment": [ - "{0} is the folder path" - ] - }, - "Collect Dump": "Collect Dump", - "Failed to collect dump: {0}/{0} is the error message": { - "message": "Failed to collect dump: {0}", + "Language server process not found, ensure the server is running.": "Language server process not found, ensure the server is running.", + "Failed to collect logs: {0}/{0} is the error message": { + "message": "Failed to collect logs: {0}", "comment": [ "{0} is the error message" ] }, - "Dump saved successfully.": "Dump saved successfully.", - "Verifying tools...": "Verifying tools...", - "Capturing C# Logs": "Capturing C# Logs", + "Open Folder": "Open Folder", + "C# logs saved successfully.": "C# logs saved successfully.", + "Record Activity": "Record Activity", + "Capture live C#, LSP trace, and Razor log output": "Capture live C#, LSP trace, and Razor log output", + "Records verbose extension logging and stops when canceled.": "Records verbose extension logging and stops when canceled.", + "Performance Trace": "Performance Trace", + "Record a dotnet-trace of the language server": "Record a dotnet-trace of the language server", + "Captures runtime events from the language server process until canceled.": "Captures runtime events from the language server process until canceled.", + "Creates a full process dump for troubleshooting and analysis.": "Creates a full process dump for troubleshooting and analysis.", + "Captures managed heap information for investigating memory issues.": "Captures managed heap information for investigating memory issues.", + "Collect C# Logs": "Collect C# Logs", + "Select additional logging to collect": "Select additional logging to collect", "Save Logs": "Save Logs", - "Failed to save C# logs: {0}/{0} is the error message": { - "message": "Failed to save C# logs: {0}", + "Output folder {0} does not exist/{0} is the folder path": { + "message": "Output folder {0} does not exist", "comment": [ - "{0} is the error message" + "{0} is the folder path" ] }, + "Creating archive...": "Creating archive...", + "Recording trace... Click Cancel to stop and save.": "Recording trace... Click Cancel to stop and save.", + "Recording logs... Click Cancel to stop and save.": "Recording logs... Click Cancel to stop and save.", + "Collecting C# Logs": "Collecting C# Logs", "Generated document not found": "Generated document not found", "Nested Code Action": "Nested Code Action", "Fix All: ": "Fix All: ", diff --git a/package.json b/package.json index 64a6ad5a0a..a78903bf01 100644 --- a/package.json +++ b/package.json @@ -1952,20 +1952,8 @@ "enablement": "isWorkspaceTrusted && dotnet.server.activationContext == 'OmniSharp'" }, { - "command": "csharp.recordLanguageServerTrace", - "title": "%command.csharp.recordLanguageServerTrace%", - "category": "CSharp", - "enablement": "isWorkspaceTrusted && (dotnet.server.activationContext == 'Roslyn' || dotnet.server.activationContext == 'RoslynDevKit')" - }, - { - "command": "csharp.captureLogs", - "title": "%command.csharp.captureLogs%", - "category": "CSharp", - "enablement": "isWorkspaceTrusted && (dotnet.server.activationContext == 'Roslyn' || dotnet.server.activationContext == 'RoslynDevKit')" - }, - { - "command": "csharp.collectDump", - "title": "%command.csharp.collectDump%", + "command": "csharp.collectLogs", + "title": "%command.csharp.collectLogs%", "category": "CSharp", "enablement": "isWorkspaceTrusted && (dotnet.server.activationContext == 'Roslyn' || dotnet.server.activationContext == 'RoslynDevKit')" }, diff --git a/package.nls.cs.json b/package.nls.cs.json index 0b771db689..a4b3ee6d77 100644 --- a/package.nls.cs.json +++ b/package.nls.cs.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Připojení k procesu .NET 5+ nebo .NET Core", - "command.csharp.captureLogs": "Zachytávat výstup protokolu", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Změna kontextu projektu aktivního dokumentu", "command.csharp.changeProjectContextEditor": "Vybrat kontext projektu", "command.csharp.changeProjectContextFileExplorer": "Vybrat kontext projektu", - "command.csharp.collectDump": "Shromáždit výpis paměti jazykového serveru C#", "command.csharp.downloadDebugger": "Stáhnout ladicí program .NET Core", "command.csharp.listProcess": "Vypsat proces pro připojení", "command.csharp.listRemoteDockerProcess": "Výpis procesů v připojení Dockeru", "command.csharp.listRemoteProcess": "Vypsat procesy pro připojení na vzdáleném připojení", - "command.csharp.recordLanguageServerTrace": "Záznam trasování výkonu jazykového serveru C#", "command.csharp.reportIssue": "Nahlásit problém", "command.csharp.rerunSourceGenerators": "Znovu spustit zdrojové generátory", "command.csharp.showDecompilationTerms": "Zobrazit smlouvu o podmínkách dekompilátoru", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Při nastavení této možnosti se text, který cílová aplikace zapisuje do stdout a stderr (např. Console.WriteLine), uloží do zadaného souboru. Tato možnost se bude ignorovat, pokud je konzola nastavená na jinou hodnotu než internalConsole. Příklad: ${workspaceFolder}/out.txt", "generateOptionsSchema.type.markdownDescription": "Typ kódu, který se má ladit. Může to být buď coreclr pro ladění .NET Core, nebo cclr pro Desktop .NET Framework. clr funguje pouze v systému Windows, protože Desktop Framework je určen pouze pro Windows.", "viewsWelcome.debug.contents": "[Generování prostředků jazyka C# pro sestavení a ladění](command:dotnet.generateAssets)\r\n\r\nDalší informace o souboru launch.json najdete v tématu [Konfigurace souboru launch.json pro ladění v jazyce C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.de.json b/package.nls.de.json index a71067de5c..5201bd889d 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "An einen .NET 5+ oder .NET Core-Prozess anfügen", - "command.csharp.captureLogs": "Protokollausgabe erfassen", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Projektkontext des aktiven Dokuments ändern", "command.csharp.changeProjectContextEditor": "Projektkontext auswählen", "command.csharp.changeProjectContextFileExplorer": "Projektkontext auswählen", - "command.csharp.collectDump": "Speicherabbild des C#-Computersprachservers erfassen", "command.csharp.downloadDebugger": ".NET Core-Debugger herunterladen", "command.csharp.listProcess": "Prozess zum Anfügen auflisten", "command.csharp.listRemoteDockerProcess": "Prozesse für Docker-Verbindung auflisten", "command.csharp.listRemoteProcess": "Prozesse für Remoteverbindung zum Anfügen auflisten", - "command.csharp.recordLanguageServerTrace": "Leistungsablaufverfolgung des C#-Sprachservers aufzeichnen", "command.csharp.reportIssue": "Ein Problem melden", "command.csharp.rerunSourceGenerators": "Quellgeneratoren erneut ausführen", "command.csharp.showDecompilationTerms": "Vereinbarung zu den Decompilerbedingungen anzeigen", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Bei Festlegung wird Text, den die Zielanwendung in \"stdout\" und \"stderr\" (z. B. Console.WriteLine) schreibt, in der angegebenen Datei gespeichert. Diese Option wird ignoriert, wenn die Konsole auf einen anderen Wert als internalConsole festgelegt ist. Beispiel: \"${workspaceFolder}/out.txt\"", "generateOptionsSchema.type.markdownDescription": "Geben Sie den Typ des zu debuggenden Codes ein. Dies kann `coreclr` für das .NET Core-Debugging oder `clr` für Desktop .NET Framework sein. `clr` funktioniert nur für Windows, da das Desktopframework nur Windows ist.", "viewsWelcome.debug.contents": "[C#-Objekte für Build und Debuggen generieren](command:dotnet.generateAssets)\r\n\r\nWeitere Informationen zu launch.json finden Sie unter [Konfigurieren von launch.json für das C#-Debuggen](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.es.json b/package.nls.es.json index 8f647b3432..0d02f4fcf8 100644 --- a/package.nls.es.json +++ b/package.nls.es.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Adjuntar a un proceso de .NET 5 (o posterior) o .NET Core", - "command.csharp.captureLogs": "Capturar salida de registro", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Cambiar el contexto del proyecto del documento activo", "command.csharp.changeProjectContextEditor": "Seleccionar contexto de proyecto", "command.csharp.changeProjectContextFileExplorer": "Seleccionar contexto de proyecto", - "command.csharp.collectDump": "Recopilación de un volcado del servidor de lenguaje C#", "command.csharp.downloadDebugger": "Descargar depurador de .NET Core", "command.csharp.listProcess": "Enumerar proceso que se va a adjuntar", "command.csharp.listRemoteDockerProcess": "Enumerar procesos en conexión de Docker", "command.csharp.listRemoteProcess": "Enumerar procesos en conexión remota para adjuntar", - "command.csharp.recordLanguageServerTrace": "Registro del seguimiento del rendimiento del servidor de Lenguaje C#", "command.csharp.reportIssue": "Informar de un problema", "command.csharp.rerunSourceGenerators": "Volver a ejecutar los generadores de origen", "command.csharp.showDecompilationTerms": "Mostrar el contrato de términos del descompilador", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Cuando se establece, el texto que la aplicación de destino escribe en stdout y stderr (por ejemplo, Console.WriteLine) se guardará en el archivo especificado. Esta opción se omite si la consola se establece en un valor distinto de internalConsole. Por ejemplo, \"${workspaceFolder}/out.txt\"", "generateOptionsSchema.type.markdownDescription": "Escriba el tipo de código que se va a depurar. Puede ser \"coreclr\" para la depuración de .NET Core o \"clr\" para desktop .NET Framework. \"clr\" solo funciona en Windows, ya que el marco de trabajo de escritorio es solo de Windows.", "viewsWelcome.debug.contents": "[Generar recursos de C# para compilación y depuración](command:dotnet.generateAssets)\r\n\r\nPara obtener más información sobre launch.json, consulte [Configuración de launch.json para la depuración de C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.fr.json b/package.nls.fr.json index a8e18c135c..cce21f9859 100644 --- a/package.nls.fr.json +++ b/package.nls.fr.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Attacher à un processus .NET 5+ ou .NET Core", - "command.csharp.captureLogs": "Capturer la sortie des journaux", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Modifier le contexte du projet du document actif", "command.csharp.changeProjectContextEditor": "Sélectionner le contexte du projet", "command.csharp.changeProjectContextFileExplorer": "Sélectionner le contexte du projet", - "command.csharp.collectDump": "Collecter un vidage du serveur de langage C#", "command.csharp.downloadDebugger": "Télécharger le débogueur .NET Core", "command.csharp.listProcess": "Processus de liste pour la pièce jointe", "command.csharp.listRemoteDockerProcess": "Répertorier les processus sur la connexion Docker", "command.csharp.listRemoteProcess": "Répertorier les processus sur la connexion à distance pour la pièce jointe", - "command.csharp.recordLanguageServerTrace": "Enregistrer une trace de performance du serveur de langage C#", "command.csharp.reportIssue": "Signaler un problème", "command.csharp.rerunSourceGenerators": "Réexécuter les générateurs sources", "command.csharp.showDecompilationTerms": "Afficher l'accord sur les termes du décompilateur", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Lorsqu’il est défini, le texte écrit par l’application cible dans stdout et stderr (par exemple, Console.WriteLine) est enregistré dans le fichier spécifié. Cette option est ignorée si la console a une valeur autre que internalConsole. Exemple : « ${workspaceFolder}/out.txt »", "generateOptionsSchema.type.markdownDescription": "Type de code à déboguer. Peut être soit « coreclr » pour le débogage .NET Core, soit « clr » pour Desktop .NET Framework. `clr` ne fonctionne que sous Windows car le framework Desktop est uniquement Windows.", "viewsWelcome.debug.contents": "[Générer des ressources C# pour la génération et le débogage](command:dotnet.generateAssets)\r\n\r\nPour en savoir plus sur launch.json, consultez [Configuration de launch.json pour le débogage C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.it.json b/package.nls.it.json index fad63fda7a..8d68aa2f73 100644 --- a/package.nls.it.json +++ b/package.nls.it.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Allega a un processo .NET 5+ o .NET Core", - "command.csharp.captureLogs": "Acquisisci output log", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Modifica il contesto del progetto del documento attivo", "command.csharp.changeProjectContextEditor": "Selezionare contesto progetto", "command.csharp.changeProjectContextFileExplorer": "Selezionare contesto progetto", - "command.csharp.collectDump": "Raccogliere un dump del server di linguaggio C#", "command.csharp.downloadDebugger": "Scarica il debugger di .NET Core", "command.csharp.listProcess": "Elenca i processi per il collegamento", "command.csharp.listRemoteDockerProcess": "Elenca i processi nella connessione Docker", "command.csharp.listRemoteProcess": "Elenca i processi nella connessione remota per il collegamento", - "command.csharp.recordLanguageServerTrace": "Registra una traccia delle prestazioni del server di linguaggio C#", "command.csharp.reportIssue": "Segnala un problema", "command.csharp.rerunSourceGenerators": "Esegui di nuovo generatori di codice sorgente", "command.csharp.showDecompilationTerms": "Mostra il contratto per i termini del decompilatore", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Quando questa opzione è impostata, il testo che l'applicazione di destinazione scrive in stdout e stderr, ad esempio Console.WriteLine, verrà salvato nel file specificato. Questa opzione viene ignorata se la console è impostata su un valore diverso da internalConsole. Ad esempio '${workspaceFolder}/out.txt'.", "generateOptionsSchema.type.markdownDescription": "Digitare il tipo di codice di cui eseguire il debug. Può essere `coreclr` per il debug di .NET Core o `clr` per .NET Framework desktop. `clr` funziona solo su Windows poiché il framework desktop è solo per Windows.", "viewsWelcome.debug.contents": "[Genera risorse C# per compilazione e debug](command:dotnet.generateAssets)\r\n\r\nPer altre informazioni su launch.json, vedere [Configurazione di launch.json per il debug di C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.ja.json b/package.nls.ja.json index 4f88f72082..baf217a4c3 100644 --- a/package.nls.ja.json +++ b/package.nls.ja.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": ".NET 5 以降または .NET Core プロセスにアタッチする", - "command.csharp.captureLogs": "ログ出力のキャプチャ", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "アクティブなドキュメントのプロジェクト コンテキストを変更する", "command.csharp.changeProjectContextEditor": "プロジェクト コンテキストの選択", "command.csharp.changeProjectContextFileExplorer": "プロジェクト コンテキストの選択", - "command.csharp.collectDump": "C# 言語サーバーのダンプを収集する", "command.csharp.downloadDebugger": ".NET Core デバッガーをダウンロードする", "command.csharp.listProcess": "アタッチのプロセスをリスト表示する", "command.csharp.listRemoteDockerProcess": "Docker 接続のプロセスをリスト表示する", "command.csharp.listRemoteProcess": "アタッチ用のリモート接続のプロセスをリスト表示する", - "command.csharp.recordLanguageServerTrace": "C# 言語サーバーのパフォーマンス トレースを記録する", "command.csharp.reportIssue": "問題の報告", "command.csharp.rerunSourceGenerators": "ソース ジェネレーターの再実行", "command.csharp.showDecompilationTerms": "逆コンパイラの使用契約条件を表示する", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "設定すると、ターゲット アプリケーションが StdOut および stderr (例: Console.WriteLine) に書き込むテキストが指定したファイルに保存されます。コンソールが internalConsole 以外に設定されている場合、このオプションは無視されます。例: '${workspaceFolder}/out.txt'", "generateOptionsSchema.type.markdownDescription": "デバッグするコードの種類を入力します。.NET Core デバッグの場合は `coreclr`、デスクトップ .NET Framework の場合は `clr` のいずれかを指定できます。デスクトップ フレームワークは Windows 専用であるため、`clr` は Windows でのみ動作します。", "viewsWelcome.debug.contents": "[ビルドおよびデバッグ用の C# 資産の生成](command:dotnet.generateAssets)\r\n\r\nlaunch.json の詳細については、[C# デバッグ用の launch.json の構成](https://aka.ms/VSCode-CS-LaunchJson). を参照してください。" -} \ No newline at end of file +} diff --git a/package.nls.json b/package.nls.json index 177c894aef..5828561c92 100644 --- a/package.nls.json +++ b/package.nls.json @@ -21,9 +21,7 @@ "command.csharp.reportIssue": "Report an issue", "command.csharp.rerunSourceGenerators": "Rerun Source Generators", "command.csharp.showDecompilationTerms": "Show the decompiler terms agreement", - "command.csharp.recordLanguageServerTrace": "Record a performance trace of the C# Language Server", - "command.csharp.captureLogs": "Capture Log Output", - "command.csharp.collectDump": "Collect a dump of the C# Language Server", + "command.csharp.collectLogs": "Collect C# Logs", "command.extension.showRazorCSharpWindow": "Show Razor CSharp", "command.extension.showRazorHtmlWindow": "Show Razor Html", "command.razor.reportIssue": "Report a Razor issue", diff --git a/package.nls.ko.json b/package.nls.ko.json index 5e1490b7a6..dde4bd139a 100644 --- a/package.nls.ko.json +++ b/package.nls.ko.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": ".NET 5+ 또는 .NET Core 프로세스에 연결", - "command.csharp.captureLogs": "로그 출력 캡처", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "활성 문서의 프로젝트 컨텍스트 변경", "command.csharp.changeProjectContextEditor": "프로젝트 컨텍스트 선택", "command.csharp.changeProjectContextFileExplorer": "프로젝트 컨텍스트 선택", - "command.csharp.collectDump": "C# 언어 서버의 덤프 수집", "command.csharp.downloadDebugger": ".NET Core 디버거 다운로드", "command.csharp.listProcess": "연결 프로세스 나열", "command.csharp.listRemoteDockerProcess": "Docker 연결에 프로세스 나열", "command.csharp.listRemoteProcess": "첨부할 원격 연결의 프로세스 나열", - "command.csharp.recordLanguageServerTrace": "C# 언어 서버의 성능 추적 기록", "command.csharp.reportIssue": "문제 신고", "command.csharp.rerunSourceGenerators": "원본 생성기 다시 실행", "command.csharp.showDecompilationTerms": "디컴파일러 계약 표시", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "설정하면 대상 애플리케이션이 stdout 및 stderr(예: Console.WriteLine)에 쓰는 텍스트가 지정된 파일에 저장됩니다. 콘솔이 internalConsole 이외의 것으로 설정된 경우 이 옵션은 무시됩니다(예: '${workspaceFolder}/out.txt')", "generateOptionsSchema.type.markdownDescription": "디버깅할 코드 형식입니다. .NET Core 디버깅의 경우 `coreclr`, 데스크톱 .NET Framework의 경우 `clr`일 수 있습니다. 데스크톱 프레임워크는 Windows 전용이므로 `clr`은 Windows에서만 작동합니다.", "viewsWelcome.debug.contents": "[빌드와 디버그를 위한 C# 자산 생성](command:dotnet.generateAssets)\r\n\r\nlaunch.json에 관해 자세히 알아보려면 [C# 디버깅을 위한 launch.json 구성](https://aka.ms/VSCode-CS-LaunchJson)을 참조하세요." -} \ No newline at end of file +} diff --git a/package.nls.pl.json b/package.nls.pl.json index 7a8b648f7f..97805eec29 100644 --- a/package.nls.pl.json +++ b/package.nls.pl.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Dołączanie do procesu platformy .NET 5 lub .NET Core", - "command.csharp.captureLogs": "Przechwyć dane wyjściowe dziennika", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Zmień kontekst projektu dokumentu aktywnego", "command.csharp.changeProjectContextEditor": "Wybierz kontekst projektu", "command.csharp.changeProjectContextFileExplorer": "Wybierz kontekst projektu", - "command.csharp.collectDump": "Zbierz zrzut serwera języka C#", "command.csharp.downloadDebugger": "Pobierz debuger platformy .NET Core", "command.csharp.listProcess": "Wyświetl proces do dołączenia", "command.csharp.listRemoteDockerProcess": "Wyświetl listę procesów w połączeniu platformy Docker", "command.csharp.listRemoteProcess": "Wyświetl listę procesów w połączeniu zdalnym do dołączenia", - "command.csharp.recordLanguageServerTrace": "Rejestrowanie śledzenia wydajności serwera języka C#", "command.csharp.reportIssue": "Zgłoś problem", "command.csharp.rerunSourceGenerators": "Ponownie uruchom generatory źródeł", "command.csharp.showDecompilationTerms": "Pokaż umowę warunków dekompilowania", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Po ustawieniu tekst, który aplikacja docelowa zapisuje w stdout i stderr (np. Console.WriteLine), zostanie zapisany w określonym pliku. Ta opcja jest ignorowana, jeśli konsola jest ustawiona na wartość inną niż internalConsole, np. \"${workspaceFolder}/out.txt\"", "generateOptionsSchema.type.markdownDescription": "Wpisz typ kodu do debugowania. Może to być „coreclr” na potrzeby debugowania platformy .NET Core lub „clr” dla platformy klasycznej .NET Framework. Element „clr” działa tylko w systemie Windows, ponieważ platforma klasyczna działa tylko w systemie Windows.", "viewsWelcome.debug.contents": "[Generuj zasoby języka C# na potrzeby kompilacji i debugowania](polecenie:dotnet.generateAssets)\r\n\r\nAby dowiedzieć się więcej o uruchamianiu pliku launch.json, zobacz [Konfigurowanie pliku launch.json na potrzeby debugowania w języku C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.pt-br.json b/package.nls.pt-br.json index 8b217f97f0..e73a284125 100644 --- a/package.nls.pt-br.json +++ b/package.nls.pt-br.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Anexar a um processo .NET 5+ ou .NET Core", - "command.csharp.captureLogs": "Capturar Resultado do Log", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Alterar o contexto do projeto do documento ativo", "command.csharp.changeProjectContextEditor": "Selecionar Contexto do Projeto", "command.csharp.changeProjectContextFileExplorer": "Selecionar Contexto do Projeto", - "command.csharp.collectDump": "Recolher um despejo do Servidor de Linguagem C#", "command.csharp.downloadDebugger": "Baixar o Depurador .NET Core", "command.csharp.listProcess": "Listar processo para anexar", "command.csharp.listRemoteDockerProcess": "Listar processos na conexão Docker", "command.csharp.listRemoteProcess": "Listar processos em conexão remota para anexar", - "command.csharp.recordLanguageServerTrace": "Gravar um rastreamento de desempenho do Servidor de Linguagem C#", "command.csharp.reportIssue": "Relatar um problema", "command.csharp.rerunSourceGenerators": "Executar novamente geradores de origem", "command.csharp.showDecompilationTerms": "Mostrar o contrato de termos do descompilador", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Quando definido, o texto que o aplicativo de destino grava em stdout e stderr (ex: Console.WriteLine) será salvo no arquivo especificado. Essa opção será ignorada se o console for definido como algo diferente de internalConsole. Por exemplo. '${workspaceFolder}/out.txt'", "generateOptionsSchema.type.markdownDescription": "Tipo de código a ser depurado. Pode ser \"coreclr\" para a depuração do .NET Core ou \"clr\" para o .NET Framework para desktop. O \"clr\" só funciona no Windows, pois o Desktop Framework é exclusivo do Windows.", "viewsWelcome.debug.contents": "[Gerar ativos C# para Build e Depuração](command:dotnet.generateAssets)\r\n\r\nPara saber mais sobre launch.json, consulte [Como configurar launch.json para depuração C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.ru.json b/package.nls.ru.json index 563449ac9a..cdc65497b6 100644 --- a/package.nls.ru.json +++ b/package.nls.ru.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "Присоединение к процессу .NET 5+ или .NET Core.", - "command.csharp.captureLogs": "Сбор выходных данных журнала", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Изменить контекст проекта активного документа", "command.csharp.changeProjectContextEditor": "Выбор контекста проекта", "command.csharp.changeProjectContextFileExplorer": "Выбор контекста проекта", - "command.csharp.collectDump": "Сбор дампа языкового сервера C#", "command.csharp.downloadDebugger": "Скачать отладчик .NET Core", "command.csharp.listProcess": "Перечислить процесс для вложения", "command.csharp.listRemoteDockerProcess": "Список процессов подключения к Docker", "command.csharp.listRemoteProcess": "Список процессов при удаленном подключении для вложения", - "command.csharp.recordLanguageServerTrace": "Записать трассировку производительности языкового сервера C#", "command.csharp.reportIssue": "Сообщить о проблеме", "command.csharp.rerunSourceGenerators": "Повторный запуск исходных генераторов", "command.csharp.showDecompilationTerms": "Показать соглашение об условиях декомпиляции", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Если этот параметр настроен, текст, который целевое приложение записывает в stdout и stderr (например, Console.WriteLine), будет сохранен в указанном файле. Этот параметр игнорируется, если для консоли настроено значение, отличное от internalConsole. Например, \"${workspaceFolder}/out.txt\"", "generateOptionsSchema.type.markdownDescription": "Введите тип кода для отладки. Может быть либо \"coreclr\" для отладки .NET Core, либо \"clr\" для классической .NET Framework. \"clr\" работает только в Windows, так как классическая платформа предназначена только для Windows.", "viewsWelcome.debug.contents": "[Создание ресурсов C# для сборки и отладки](command:dotnet.generateAssets)\r\n\r\nДополнительные сведения о launch.json см. в разделе [Настройка launch.json для отладки C#](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.tr.json b/package.nls.tr.json index edc845ae57..5c736f9c5d 100644 --- a/package.nls.tr.json +++ b/package.nls.tr.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": ".NET 5+ veya .NET Core işlemine ekleme", - "command.csharp.captureLogs": "Günlük Çıkışını Yakala", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "Etkin belgenin proje bağlamını değiştirin", "command.csharp.changeProjectContextEditor": "Proje Bağlamı Seç", "command.csharp.changeProjectContextFileExplorer": "Proje Bağlamı Seç", - "command.csharp.collectDump": "C# Dil Sunucusunun dökümünü topla", "command.csharp.downloadDebugger": ".NET Core Hata Ayıklayıcısını İndirin", "command.csharp.listProcess": "Ekleme işlemini listele", "command.csharp.listRemoteDockerProcess": "Docker bağlantısındaki işlemleri listeleme", "command.csharp.listRemoteProcess": "Eklemek için uzak bağlantıdaki işlemleri listeleyin", - "command.csharp.recordLanguageServerTrace": "C# Dil Sunucusunun performans izlemesini kaydet", "command.csharp.reportIssue": "Bir sorun bildirin", "command.csharp.rerunSourceGenerators": "Kaynak Oluşturucuları Yeniden Çalıştır", "command.csharp.showDecompilationTerms": "Derleyici koşulları sözleşmesini göster", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "Ayarlandığında hedef uygulamanın stdout ve stderr'a (ör. Console.WriteLine) yazdığı metin belirtilen dosyaya kaydedilir. Konsol, internalConsole dışında bir değere ayarlanmışsa bu seçenek yoksayılır. Ör. '${workspaceFolder}/out.txt'", "generateOptionsSchema.type.markdownDescription": "Hata ayıklamak için kodun türünü yazın. NET Core hata ayıklama için `coreclr` veya Masaüstü .NET Framework için `clr` olabilir. Masaüstü çerçevesi yalnızca Windows'a özel olduğundan `clr` yalnızca Windows'ta çalışır.", "viewsWelcome.debug.contents": "[Derleme ve Hata Ayıklama için C# Varlıkları Oluşturma](command:dotnet.generateAssets)\r\n\r\nlaunch.json hakkında daha fazla bilgi edinmek için bkz. [C# hata ayıklaması için launch.json yapılandırma](https://aka.ms/VSCode-CS-LaunchJson)." -} \ No newline at end of file +} diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 695afd1dec..4467dd952a 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "附加到 .NET 5+ 或 .NET Core 进程", - "command.csharp.captureLogs": "捕获日志输出", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "更改活动文档的项目上下文", "command.csharp.changeProjectContextEditor": "选择项目上下文", "command.csharp.changeProjectContextFileExplorer": "选择项目上下文", - "command.csharp.collectDump": "收集 C# 语言服务器的转储", "command.csharp.downloadDebugger": "下载 .NET Core 调试程序", "command.csharp.listProcess": "列出要附加的进程", "command.csharp.listRemoteDockerProcess": "列出 Docker 连接上的进程", "command.csharp.listRemoteProcess": "列出远程连接上要附加的进程", - "command.csharp.recordLanguageServerTrace": "记录 C# 语言服务器的性能跟踪", "command.csharp.reportIssue": "报告问题", "command.csharp.rerunSourceGenerators": "重新运行源生成器", "command.csharp.showDecompilationTerms": "显示反编译程序条款协议", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "设置后,目标应用程序写入 stdout 和 stderr (例如 Console.WriteLine) 的文本将保存到指定的文件。如果控制台设置为 internalConsole 以外的其他内容,则忽略此选项。例如 \"${workspaceFolder}/out.txt\"", "generateOptionsSchema.type.markdownDescription": "键入要调试的代码类型。可以是用于 .NET Core 调试的“coreclr”,也可以是用于桌面 .NET Framework 的“clr”。由于桌面框架仅适用于 Windows,因此“clr”仅适用于 Windows。", "viewsWelcome.debug.contents": "[为版本和调试生成 C# 资产](command:dotnet.generateAssets)\r\n\r\n若要了解有关 launch.json 的详细信息,请参阅 [为 C# 调试配置 launch.json](https://aka.ms/VSCode-CS-LaunchJson)。" -} \ No newline at end of file +} diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 120eacbcdb..7404cb87f4 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -1,15 +1,13 @@ { "command.csharp.attachToProcess": "連結至 .NET 5+ 或 .NET Core 程序", - "command.csharp.captureLogs": "擷取記錄輸出", + "command.csharp.collectLogs": "Collect C# Logs", "command.csharp.changeProjectContext": "變更使用中文件的專案内容", "command.csharp.changeProjectContextEditor": "選取專案内容", "command.csharp.changeProjectContextFileExplorer": "選取專案内容", - "command.csharp.collectDump": "收集 C# 語言伺服器的傾印", "command.csharp.downloadDebugger": "下載 .NET Core 偵錯工具", "command.csharp.listProcess": "列出連結的程序", "command.csharp.listRemoteDockerProcess": "列出 Docker 連線上的程序", "command.csharp.listRemoteProcess": "列出遠端連線上連結的程序", - "command.csharp.recordLanguageServerTrace": "記錄 C# 語言伺服器的效能追蹤", "command.csharp.reportIssue": "回報問題", "command.csharp.rerunSourceGenerators": "重新執行來源產生器", "command.csharp.showDecompilationTerms": "顯示解編程式條款合約", @@ -267,4 +265,4 @@ "generateOptionsSchema.targetOutputLogPath.description": "設定時,目標應用程式寫入 stdout 和 stderr 的文字 (例如: Console.WriteLine) 將會儲存到指定的檔案。如果主控台設定為 internalConsole 以外的項目,則會略過此選項。例如 '${workspaceFolder}/out.txt'", "generateOptionsSchema.type.markdownDescription": "輸入要進行偵錯的程式碼類型。可以是 `coreclr` 表示 .NET Core 偵錯,或 `clr` 表示桌面 .NET Framework。`clr` 僅在 Windows 上運作,因為桌面 Framework 僅限 Windows。", "viewsWelcome.debug.contents": "[為組建和偵錯產生 C# 資產](command:dotnet.generateAssets)\r\n\r\n若要深入了解 launch.json,請參閱[為 C# 偵錯設定 launch.json](https://aka.ms/VSCode-CS-LaunchJson)(英文)。" -} \ No newline at end of file +} diff --git a/src/common.ts b/src/common.ts index d3dc6d33e9..1e99216f49 100644 --- a/src/common.ts +++ b/src/common.ts @@ -78,6 +78,29 @@ ${stderr}`) }); } +export async function execFileChildProcess( + file: string, + args: string[], + workingDirectory: string | undefined, + env: NodeJS.ProcessEnv +): Promise { + return new Promise((resolve, reject) => { + cp.execFile(file, args, { cwd: workingDirectory, maxBuffer: 500 * 1024, env: env }, (error, stdout, stderr) => { + if (error) { + reject( + new Error(`${error} +${stdout} +${stderr}`) + ); + } else if (stderr && !stderr.includes('screen size is bogus')) { + reject(new Error(stderr)); + } else { + resolve(stdout); + } + }); + }); +} + export async function getUnixChildProcessIds(pid: number): Promise { return new Promise((resolve, reject) => { cp.exec('ps -A -o ppid,pid', (error, stdout, stderr) => { diff --git a/src/csharpExtensionExports.ts b/src/csharpExtensionExports.ts index 0c389ed8c3..93c5bb69ed 100644 --- a/src/csharpExtensionExports.ts +++ b/src/csharpExtensionExports.ts @@ -25,7 +25,13 @@ export interface OmnisharpExtensionExports { } export interface ActivityLogCapture extends vscode.Disposable { - getActivityLogs(): { csharpLog: string; lspTraceLog: string; razorLog: string }; + getActivityLogs(): ActivityLogResult; +} + +export interface ActivityLogResult { + csharpLog: string; + lspTraceLog: string; + razorLog: string; } export interface CSharpExtensionExports { diff --git a/src/lsptoolshost/commands.ts b/src/lsptoolshost/commands.ts index 6e3b7c821a..f68098facb 100644 --- a/src/lsptoolshost/commands.ts +++ b/src/lsptoolshost/commands.ts @@ -19,9 +19,7 @@ import { } from './projectContext/projectContextCommands'; import TelemetryReporter from '@vscode/extension-telemetry'; import { TelemetryEventNames } from '../shared/telemetryEventNames'; -import { registerCaptureLogsCommand } from './logging/captureLogs'; -import { registerTraceCommand } from './logging/profiling'; -import { registerDumpCommand } from './logging/dump'; +import { registerCollectLogsCommand } from './logging/collectLogs'; import { ObservableLogOutputChannel } from './logging/observableLogOutputChannel'; import { RazorLogger } from '../razor/src/razorLogger'; @@ -94,7 +92,12 @@ function registerExtensionCommands( context.subscriptions.push( vscode.commands.registerCommand('csharp.showOutputWindow', async () => outputChannel.show()) ); - registerCaptureLogsCommand(context, languageServer, outputChannel, csharpTraceChannel, razorLogger); - registerTraceCommand(context, languageServer, outputChannel, csharpTraceChannel, razorLogger); - registerDumpCommand(context, languageServer, outputChannel, csharpTraceChannel, razorLogger); + registerCollectLogsCommand( + context, + languageServer, + hostExecutableResolver, + outputChannel, + csharpTraceChannel, + razorLogger + ); } diff --git a/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts b/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts index 04c8bc5ecc..f8326fe096 100644 --- a/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts +++ b/src/lsptoolshost/dotnetRuntime/dotnetRuntimeExtensionResolver.ts @@ -66,10 +66,7 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver { }; if (usingDevkit) { - const toolingRuntimeHostInfo = await this.tryGetToolingRuntimeHostInfo({ - ...findPathRequest, - rejectPreviews: false, - }); + const toolingRuntimeHostInfo = await this.tryGetToolingRuntimeHostInfo(); if (toolingRuntimeHostInfo) { this.hostInfo = toolingRuntimeHostInfo; return toolingRuntimeHostInfo; @@ -98,9 +95,7 @@ export class DotnetRuntimeExtensionResolver implements IHostExecutableResolver { return hostInfo; } - private async tryGetToolingRuntimeHostInfo( - findPathRequest: IDotnetFindPathContext - ): Promise { + private async tryGetToolingRuntimeHostInfo(): Promise { // get vscode setting value for dotnet.toolingRuntimePath const toolingRuntimePath = languageServerOptions.toolingRuntimePath; if (toolingRuntimePath.length === 0) { diff --git a/src/lsptoolshost/logging/captureLogs.ts b/src/lsptoolshost/logging/captureLogs.ts deleted file mode 100644 index e67a92c3a2..0000000000 --- a/src/lsptoolshost/logging/captureLogs.ts +++ /dev/null @@ -1,128 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { RoslynLanguageServer } from '../server/roslynLanguageServer'; -import { ObservableLogOutputChannel } from './observableLogOutputChannel'; -import { createActivityLogCapture, createZipWithLogs, getDefaultSaveUri } from './loggingUtils'; -import { RazorLogger } from '../../razor/src/razorLogger'; - -/** - * Registers the command to capture log output. - */ -export function registerCaptureLogsCommand( - context: vscode.ExtensionContext, - languageServer: RoslynLanguageServer, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger -): void { - context.subscriptions.push( - vscode.commands.registerCommand('csharp.captureLogs', async () => { - await captureLogsToZip(context, languageServer, outputChannel, traceChannel, razorLogger); - }) - ); -} - -async function captureLogsToZip( - context: vscode.ExtensionContext, - languageServer: RoslynLanguageServer, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger -): Promise { - const capture = await createActivityLogCapture(languageServer, outputChannel, traceChannel, razorLogger); - - try { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t('Capturing C# Logs'), - cancellable: true, - }, - async (progress, token) => { - progress.report({ - message: vscode.l10n.t('Recording logs... Click Cancel to stop and save.'), - }); - - // Wait for the user to cancel the progress - await waitForCancellation(token); - - progress.report({ - message: vscode.l10n.t('Creating archive...'), - }); - - // Get formatted log content from the capture - const { csharpLog, lspTraceLog, razorLog } = capture.getActivityLogs(); - - // Prompt user for save location - const saveUri = await vscode.window.showSaveDialog({ - defaultUri: getDefaultSaveUri(), - filters: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'Zip files': ['zip'], - }, - saveLabel: vscode.l10n.t('Save Logs'), - title: vscode.l10n.t('Save Logs'), - }); - - if (!saveUri) { - // User cancelled the save dialog - return; - } - - try { - await createZipWithLogs( - context, - outputChannel, - traceChannel, - razorLogger, - csharpLog, - lspTraceLog, - razorLog, - saveUri.fsPath - ); - const openFolder = vscode.l10n.t('Open Folder'); - const result = await vscode.window.showInformationMessage( - vscode.l10n.t('C# logs saved successfully.'), - openFolder - ); - if (result === openFolder) { - await vscode.commands.executeCommand('revealFileInOS', saveUri); - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - await vscode.window.showErrorMessage( - vscode.l10n.t({ - message: 'Failed to save C# logs: {0}', - args: [errorMessage], - comment: ['{0} is the error message'], - }) - ); - } - } - ); - } finally { - // Always clean up observers and restore log levels - await capture.dispose(); - } -} - -/** - * Waits for the cancellation token to be triggered. - */ -export async function waitForCancellation(token: vscode.CancellationToken): Promise { - return new Promise((resolve) => { - if (token.isCancellationRequested) { - resolve(); - return; - } - - const disposable = token.onCancellationRequested(() => { - disposable.dispose(); - resolve(); - }); - }); -} diff --git a/src/lsptoolshost/logging/collectLogs.ts b/src/lsptoolshost/logging/collectLogs.ts new file mode 100644 index 0000000000..b9f067d4f3 --- /dev/null +++ b/src/lsptoolshost/logging/collectLogs.ts @@ -0,0 +1,491 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { showErrorMessageWithOptions } from '../../shared/observers/utils/showMessage'; +import { ObservableLogOutputChannel } from './observableLogOutputChannel'; +import { + DumpRequest, + LogsToCollect, + collectDumps, + createZipWithLogs, + getDefaultSaveUri, + getDumpConfig, + promptForToolArguments, + DumpType, + createActivityLogCapture, +} from './loggingUtils'; +import { runDotnetTraceInTerminal } from './profiling'; +import { RazorLogger } from '../../razor/src/razorLogger'; +import { IHostExecutableResolver } from '../../shared/constants/IHostExecutableResolver'; + +/** Represents the types of data the user can choose to collect */ +type CollectOption = 'activityLogs' | 'performanceTrace' | 'memoryDump' | 'gcDump'; + +interface CollectOptionQuickPickItem extends vscode.QuickPickItem { + option: CollectOption; +} + +/** + * Registers the unified collect logs command. + */ +export function registerCollectLogsCommand( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + hostExecutableResolver: IHostExecutableResolver, + outputChannel: ObservableLogOutputChannel, + traceChannel: ObservableLogOutputChannel, + razorLogger: RazorLogger +): void { + context.subscriptions.push( + vscode.commands.registerCommand('csharp.collectLogs', async () => { + await collectLogs( + context, + languageServer, + hostExecutableResolver, + outputChannel, + traceChannel, + razorLogger + ); + }) + ); +} + +async function collectLogs( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + hostExecutableResolver: IHostExecutableResolver, + outputChannel: ObservableLogOutputChannel, + traceChannel: ObservableLogOutputChannel, + razorLogger: RazorLogger +): Promise { + // Step 1: Let the user select which additional logs or dumps to collect. + const selectedLogs = await selectAdditionalLogs(); + if (!selectedLogs) { + return; + } + + const dumpSelected = selectedLogs.memoryDump || selectedLogs.gcDump; + + // processId is only required if the user is collecting a performance trace or memory dumps. + const processId = RoslynLanguageServer.processId ?? -1; + if (selectedLogs.performanceTrace || dumpSelected) { + // Validate process ID is available if trace or dumps are needed + if (processId === -1) { + showErrorMessageWithOptions( + vscode, + vscode.l10n.t('Language server process not found, ensure the server is running.'), + { modal: true } + ); + return; + } + } + + // Step 2: Resolve the dotnet path and let the user customize arguments for each selected tool + + // We use the resolved dotnet path instead of relying on an install being available from + // the PATH because the resolved one is guarenteed to support `dotnet dnx` which will allow + // us to not worry about whether the tools are already installed. + + const hostInfo = await hostExecutableResolver.getHostExecutableInfo(); + const dotnetPath = hostInfo.path; + const defaultSaveUri = getDefaultSaveUri('csharp-logs'); + + const toolConfigs = await prepareTools( + processId, + selectedLogs.performanceTrace, + selectedLogs.memoryDump, + selectedLogs.gcDump + ); + if (!toolConfigs) { + return; + } + + // Step 3: Prompt for save location + const zipFile = await chooseSaveLocation(defaultSaveUri); + if (!zipFile) { + return; + } + + // Step 4: Execute the collection + const archiveResult = + selectedLogs.activityLogs || selectedLogs.performanceTrace + ? await archiveActivity( + context, + languageServer, + outputChannel, + traceChannel, + razorLogger, + selectedLogs, + dumpSelected, + toolConfigs.dumpConfigs, + dotnetPath, + zipFile.parentFolder, + zipFile.uri, + toolConfigs.traceArgs + ) + : await archiveDumps( + context, + outputChannel, + traceChannel, + razorLogger, + toolConfigs.dumpConfigs, + dotnetPath, + zipFile.parentFolder, + zipFile.uri + ); + + // Show result message + if (archiveResult.errorMessage) { + showErrorMessageWithOptions( + vscode, + vscode.l10n.t({ + message: 'Failed to collect logs: {0}', + args: [archiveResult.errorMessage], + comment: ['{0} is the error message'], + }), + { modal: true } + ); + return; + } + + if (archiveResult.uri) { + const openFolder = vscode.l10n.t('Open Folder'); + const result = await vscode.window.showInformationMessage( + vscode.l10n.t('C# logs saved successfully.'), + openFolder + ); + if (result === openFolder) { + await vscode.commands.executeCommand('revealFileInOS', archiveResult.uri); + } + } +} + +async function selectAdditionalLogs(): Promise { + const items: CollectOptionQuickPickItem[] = [ + { + label: vscode.l10n.t('Record Activity'), + description: vscode.l10n.t('Capture live C#, LSP trace, and Razor log output'), + detail: vscode.l10n.t('Records verbose extension logging and stops when canceled.'), + option: 'activityLogs', + }, + { + label: vscode.l10n.t('Performance Trace'), + description: vscode.l10n.t('Record a dotnet-trace of the language server'), + detail: vscode.l10n.t('Captures runtime events from the language server process until canceled.'), + option: 'performanceTrace', + }, + { + label: vscode.l10n.t('Memory Dump'), + description: vscode.l10n.t('Process memory dump using dotnet-dump'), + detail: vscode.l10n.t('Creates a full process dump for troubleshooting and analysis.'), + option: 'memoryDump', + }, + { + label: vscode.l10n.t('GC Dump'), + description: vscode.l10n.t('Garbage collector heap dump using dotnet-gcdump'), + detail: vscode.l10n.t('Captures managed heap information for investigating memory issues.'), + option: 'gcDump', + }, + ]; + + const selected = await vscode.window.showQuickPick(items, { + title: vscode.l10n.t('Collect C# Logs'), + placeHolder: vscode.l10n.t('Select additional logging to collect'), + canPickMany: true, + }); + + if (!selected) { + return undefined; + } + + const selectedOptions = new Set(selected.map((s) => s.option)); + return { + activityLogs: selectedOptions.has('activityLogs'), + performanceTrace: selectedOptions.has('performanceTrace'), + memoryDump: selectedOptions.has('memoryDump'), + gcDump: selectedOptions.has('gcDump'), + }; +} + +async function prepareTools( + processId: number, + performanceTrace: boolean, + memoryDump: boolean, + gcDump: boolean +): Promise< + | { + traceArgs: string | undefined; + dumpConfigs: DumpRequest[]; + } + | undefined +> { + let userCanceled = false; + let traceArgs: string | undefined; + const dumpConfigs: DumpRequest[] = []; + + if (performanceTrace) { + const defaultTraceArgs = `--process-id ${processId} --clreventlevel informational --providers "Microsoft-DotNETCore-SampleProfiler,Microsoft-Windows-DotNETRuntime,Microsoft-CodeAnalysis-General:0xFFFFFFFF:5,Microsoft-CodeAnalysis-Workspaces:0xFFFFFFFF:5,RoslynEventSource:0xFFFFFFFF:5"`; + traceArgs = await promptForToolArguments('dotnet-trace', defaultTraceArgs); + if (traceArgs === undefined) { + userCanceled = true; + } + } + + if (!userCanceled && memoryDump) { + const config = getDumpConfig('memory'); + const defaultArgs = config.defaultArgs.replace('{processId}', processId.toString()); + const args = await promptForToolArguments(config.toolName, defaultArgs); + if (args === undefined) { + userCanceled = true; + } else { + dumpConfigs.push({ type: 'memory' as DumpType, args }); + } + } + + if (!userCanceled && gcDump) { + const config = getDumpConfig('gc'); + const defaultArgs = config.defaultArgs.replace('{processId}', processId.toString()); + const args = await promptForToolArguments(config.toolName, defaultArgs); + if (args === undefined) { + userCanceled = true; + } else { + dumpConfigs.push({ type: 'gc' as DumpType, args }); + } + } + + if (userCanceled) { + return undefined; + } + + return { + traceArgs, + dumpConfigs, + }; +} + +async function chooseSaveLocation( + defaultSaveUri: vscode.Uri +): Promise<{ uri: vscode.Uri; parentFolder: string } | undefined> { + const uri = await vscode.window.showSaveDialog({ + defaultUri: defaultSaveUri, + filters: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Zip files': ['zip'], + }, + saveLabel: vscode.l10n.t('Save Logs'), + title: vscode.l10n.t('Save Logs'), + }); + if (!uri) { + return undefined; + } + + const parentFolder = path.dirname(uri.fsPath); + if (!fs.existsSync(parentFolder)) { + showErrorMessageWithOptions( + vscode, + vscode.l10n.t({ + message: 'Output folder {0} does not exist', + args: [parentFolder], + comment: ['{0} is the folder path'], + }), + { modal: true } + ); + return undefined; + } + + return { + uri, + parentFolder, + }; +} + +async function archiveDumps( + context: vscode.ExtensionContext, + outputChannel: ObservableLogOutputChannel, + traceChannel: ObservableLogOutputChannel, + razorLogger: RazorLogger, + dumpConfigs: DumpRequest[], + dotnetPath: string, + outputFolder: string, + saveUri: vscode.Uri +): Promise<{ uri: vscode.Uri | undefined; errorMessage: string | undefined }> { + let errorMessage: string | undefined; + let uri: vscode.Uri | undefined; + + await collectingWithProgress(/* cancellable */ false, async (progress) => { + try { + const collectedDumps = await collectDumps(dumpConfigs, dotnetPath, outputFolder, progress, outputChannel); + + progress.report({ message: vscode.l10n.t('Creating archive...') }); + await createZipWithLogs( + context, + outputChannel, + traceChannel, + razorLogger, + /* activityLogs */ undefined, + saveUri.fsPath, + undefined, + collectedDumps + ); + + uri = saveUri; + } catch (error) { + errorMessage = error instanceof Error ? error.message : String(error); + } + }); + + return { + uri, + errorMessage, + }; +} + +async function archiveActivity( + context: vscode.ExtensionContext, + languageServer: RoslynLanguageServer, + outputChannel: ObservableLogOutputChannel, + traceChannel: ObservableLogOutputChannel, + razorLogger: RazorLogger, + selectedLogs: LogsToCollect, + dumpSelected: boolean, + dumpConfigs: DumpRequest[], + dotnetPath: string, + outputFolder: string, + saveUri: vscode.Uri, + traceArgs: string | undefined +): Promise<{ uri: vscode.Uri | undefined; errorMessage: string | undefined }> { + let errorMessage: string | undefined; + let uri: vscode.Uri | undefined; + + const capture = await createActivityLogCapture(languageServer, outputChannel, traceChannel, razorLogger); + + try { + let traceFilePath: string | undefined; + const dumpFiles: string[] = []; + + if (dumpSelected) { + await collectingWithProgress(/* cancellable */ false, async (progress) => { + const startDumps = await collectDumps( + dumpConfigs, + dotnetPath, + outputFolder, + progress, + outputChannel, + 'before' + ); + dumpFiles.push(...startDumps); + }); + } + + const message = selectedLogs.performanceTrace + ? vscode.l10n.t('Recording trace... Click Cancel to stop and save.') + : vscode.l10n.t('Recording logs... Click Cancel to stop and save.'); + const collectTask = selectedLogs.performanceTrace + ? async (token: vscode.CancellationToken) => + runDotnetTraceInTerminal(dotnetPath, traceArgs!.split(' '), outputFolder, outputChannel, token) + : async (token: vscode.CancellationToken) => await waitForCancellation(token); + + await collectingWithProgress(/* cancellable */ true, async (progress, token) => { + try { + progress.report({ message }); + traceFilePath = await collectTask(token); + } catch (error) { + errorMessage = error instanceof Error ? error.message : String(error); + } + }); + + if (errorMessage) { + return { + uri, + errorMessage, + }; + } + + const activityLogs = selectedLogs.activityLogs ? capture.getActivityLogs() : undefined; + + await collectingWithProgress(/* cancellable */ false, async (progress) => { + try { + if (dumpSelected) { + const afterDumps = await collectDumps( + dumpConfigs, + dotnetPath, + outputFolder, + progress, + outputChannel, + 'after' + ); + dumpFiles.push(...afterDumps); + } + + progress.report({ + message: vscode.l10n.t('Creating archive...'), + }); + + await createZipWithLogs( + context, + outputChannel, + traceChannel, + razorLogger, + activityLogs, + saveUri.fsPath, + traceFilePath, + dumpFiles + ); + + uri = saveUri; + } catch (error) { + errorMessage = error instanceof Error ? error.message : String(error); + } + }); + } finally { + capture.dispose(); + } + + return { + uri, + errorMessage, + }; +} + +async function collectingWithProgress( + cancellable: boolean, + task: ( + progress: vscode.Progress<{ + message?: string; + increment?: number; + }>, + token: vscode.CancellationToken + ) => Promise +) { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: vscode.l10n.t('Collecting C# Logs'), + cancellable: cancellable, + }, + task + ); +} + +/** + * Waits for the cancellation token to be triggered. + */ +async function waitForCancellation(token: vscode.CancellationToken): Promise { + return new Promise((resolve) => { + if (token.isCancellationRequested) { + resolve(undefined); + return; + } + + const disposable = token.onCancellationRequested(() => { + disposable.dispose(); + resolve(undefined); + }); + }); +} diff --git a/src/lsptoolshost/logging/dump.ts b/src/lsptoolshost/logging/dump.ts deleted file mode 100644 index 67e82b049e..0000000000 --- a/src/lsptoolshost/logging/dump.ts +++ /dev/null @@ -1,176 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { RoslynLanguageServer } from '../server/roslynLanguageServer'; -import { ObservableLogOutputChannel } from './observableLogOutputChannel'; -import { - DumpRequest, - collectDumps, - createZipWithLogs, - getDefaultSaveUri, - selectDumpsWithArguments, - verifyDumpTools, -} from './loggingUtils'; -import { showErrorMessageWithOptions } from '../../shared/observers/utils/showMessage'; -import { RazorLogger } from '../../razor/src/razorLogger'; - -export function registerDumpCommand( - context: vscode.ExtensionContext, - languageServer: RoslynLanguageServer, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger -): void { - context.subscriptions.push( - vscode.commands.registerCommand('csharp.collectDump', async () => { - const processId = RoslynLanguageServer.processId; - if (!processId) { - showErrorMessageWithOptions( - vscode, - vscode.l10n.t('Language server process not found, ensure the server is running.'), - { modal: true } - ); - return; - } - - // Step 1: Let the user select dump type(s) and provide arguments - const dumpRequests = await selectDumpsWithArguments({ - title: vscode.l10n.t('Select Dump Type'), - placeHolder: vscode.l10n.t('Choose dump type(s) to collect'), - processId, - }); - if (!dumpRequests || dumpRequests.length === 0) { - return; // User cancelled - } - - // Step 2: Prompt the user for save location - const saveUri = await vscode.window.showSaveDialog({ - defaultUri: getDefaultSaveUri('csharp-dump'), - filters: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'Zip files': ['zip'], - }, - saveLabel: vscode.l10n.t('Save Dump'), - title: vscode.l10n.t('Save Dump'), - }); - if (!saveUri) { - return; // User cancelled - } - - const dumpFolder = path.dirname(saveUri.fsPath); - if (!fs.existsSync(dumpFolder)) { - showErrorMessageWithOptions( - vscode, - vscode.l10n.t({ - message: 'Folder for dump file {0} does not exist', - args: [dumpFolder], - comment: ['{0} is the folder path'], - }), - { modal: true } - ); - return; - } - - // Step 3: Collect dumps and create archive with progress - let errorMessage: string | undefined; - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: vscode.l10n.t('Collect Dump'), - cancellable: false, - }, - async (progress) => { - try { - await executeDumpCollection( - context, - dumpRequests, - dumpFolder, - saveUri, - progress, - outputChannel, - traceChannel, - razorLogger - ); - } catch (error) { - errorMessage = error instanceof Error ? error.message : String(error); - } - } - ); - - // Show messages after progress is dismissed - if (errorMessage) { - showErrorMessageWithOptions( - vscode, - vscode.l10n.t({ - message: 'Failed to collect dump: {0}', - args: [errorMessage], - comment: ['{0} is the error message'], - }), - { modal: true } - ); - } else { - const openFolder = vscode.l10n.t('Open Folder'); - const result = await vscode.window.showInformationMessage( - vscode.l10n.t('Dump saved successfully.'), - openFolder - ); - if (result === openFolder) { - await vscode.commands.executeCommand('revealFileInOS', saveUri); - } - } - }) - ); -} - -/** - * Executes the dump collection and archiving. - */ -async function executeDumpCollection( - context: vscode.ExtensionContext, - dumpRequests: DumpRequest[], - dumpFolder: string, - saveUri: vscode.Uri, - progress: vscode.Progress<{ message?: string; increment?: number }>, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger -): Promise { - // Verify tools are installed - progress.report({ - message: vscode.l10n.t('Verifying tools...'), - }); - - const toolsVerified = await verifyDumpTools(dumpRequests, dumpFolder, progress, outputChannel); - if (!toolsVerified) { - throw new Error(vscode.l10n.t('Required dump tools could not be installed.')); - } - - // Collect all dumps - const collectedDumps = await collectDumps(dumpRequests, dumpFolder, progress, outputChannel); - - // Collect logs and create archive - progress.report({ - message: vscode.l10n.t('Creating archive...'), - }); - - // Use createZipWithLogs which handles log files, settings, and additional files - // Pass empty strings for activity logs since we're not capturing activity during this command - await createZipWithLogs( - context, - outputChannel, - traceChannel, - razorLogger, - '', // No activity log content for dump command - '', // No activity log content for dump command - '', // No activity log content for dump command - saveUri.fsPath, - undefined, // No trace file - collectedDumps // Dump files as additional files (will be cleaned up) - ); -} diff --git a/src/lsptoolshost/logging/loggingUtils.ts b/src/lsptoolshost/logging/loggingUtils.ts index c910a05882..55de5eae01 100644 --- a/src/lsptoolshost/logging/loggingUtils.ts +++ b/src/lsptoolshost/logging/loggingUtils.ts @@ -7,10 +7,10 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import archiver from 'archiver'; -import { execChildProcess } from '../../common'; +import { execFileChildProcess } from '../../common'; import { Message, ObservableLogOutputChannel } from './observableLogOutputChannel'; import { RazorLogger } from '../../razor/src/razorLogger'; -import { ActivityLogCapture } from '../../csharpExtensionExports'; +import { ActivityLogCapture, ActivityLogResult } from '../../csharpExtensionExports'; import { RoslynLanguageServer } from '../server/roslynLanguageServer'; /** @@ -62,6 +62,7 @@ export interface DumpRequest { /** * Collects dumps based on the provided requests. * @param dumpRequests The dump requests with arguments + * @param dotnetPath The path to the dotnet executable * @param folder The folder to save dumps to * @param progress Progress reporter * @param outputChannel Output channel for logging @@ -70,6 +71,7 @@ export interface DumpRequest { */ export async function collectDumps( dumpRequests: DumpRequest[], + dotnetPath: string, folder: string, progress: vscode.Progress<{ message?: string; increment?: number }>, outputChannel: ObservableLogOutputChannel, @@ -89,6 +91,7 @@ export async function collectDumps( config.toolName, config.fileExtension, request.args, + dotnetPath, folder, outputChannel, filePrefix @@ -170,32 +173,6 @@ export async function selectDumpsWithArguments(options: SelectDumpsOptions): Pro return dumpRequests; } -/** - * Verifies that all dump tools are installed for the given requests. - * @param dumpRequests The dump requests to verify tools for - * @param folder The folder to run tool verification in - * @param progress Progress reporter - * @param outputChannel Output channel for logging - * @returns True if all tools are installed, false if cancelled or failed - */ -export async function verifyDumpTools( - dumpRequests: DumpRequest[], - folder: string, - progress: vscode.Progress<{ message?: string; increment?: number }>, - outputChannel: ObservableLogOutputChannel -): Promise { - // Get unique tool names to avoid verifying the same tool twice - const toolNames = new Set(dumpRequests.map((r) => getDumpConfig(r.type).toolName)); - - for (const toolName of toolNames) { - const toolInstalled = await verifyOrAcquireDotnetTool(toolName, folder, progress, outputChannel); - if (!toolInstalled) { - return false; - } - } - return true; -} - /** * Prompts the user for tool arguments with customizable defaults. * @param toolName The name of the tool (displayed in the input box title) @@ -219,82 +196,12 @@ export async function promptForToolArguments(toolName: string, defaultArgs: stri }); } -/** - * Verifies that a dotnet global tool is installed, and prompts the user to install it if not. - * @param toolName The name of the dotnet tool (e.g., 'dotnet-trace', 'dotnet-dump', 'dotnet-gcdump') - * @param folder The folder to run the command in - * @param progress The progress reporter to update during installation - * @param channel The output channel for logging - * @returns True if the tool is installed (or was successfully installed), false otherwise - */ -export async function verifyOrAcquireDotnetTool( - toolName: string, - folder: string, - progress: vscode.Progress<{ - message?: string; - increment?: number; - }>, - channel: ObservableLogOutputChannel -): Promise { - try { - await execChildProcess(`${toolName} --version`, folder, process.env); - return true; // If the command succeeds, the tool is installed. - } catch (error) { - channel.debug(`Failed to execute ${toolName} --version with error: ${error}`); - } - - const confirmAction = { - title: vscode.l10n.t('Install'), - }; - const installCommand = `dotnet tool install --global ${toolName}`; - const confirmResult = await vscode.window.showInformationMessage( - vscode.l10n.t({ - message: '{0} not found, run "{1}" to install it?', - args: [toolName, installCommand], - comment: ['{0} is the tool name and should not be localized', '{1} is the install command'], - }), - { - modal: true, - }, - confirmAction - ); - - if (confirmResult !== confirmAction) { - return false; - } - - progress.report({ - message: vscode.l10n.t({ - message: 'Installing {0}...', - args: [toolName], - comment: ['{0} is the tool name and should not be localized'], - }), - }); - - try { - await execChildProcess(installCommand, folder, process.env); - return true; - } catch (error) { - channel.error(`Failed to install ${toolName} with error: ${error}`); - await vscode.window.showErrorMessage( - vscode.l10n.t({ - message: 'Failed to install {0}, it may need to be manually installed. See C# output for details.', - args: [toolName], - comment: ['{0} is the tool name and should not be localized'], - }), - { - modal: true, - } - ); - return false; - } -} - /** * Collects a dump using a dotnet diagnostic tool. * @param toolName The name of the dotnet tool (e.g., 'dotnet-dump', 'dotnet-gcdump') * @param fileExtension The file extension for the dump file (e.g., 'dmp', 'gcdump') * @param userArgs The user-provided arguments for the tool + * @param dotnetPath The path to the dotnet executable * @param dumpFolder The folder to write the dump file to * @param channel The output channel for logging * @param filePrefix Optional prefix for the dump file name (defaults to 'csharp-lsp') @@ -304,6 +211,7 @@ export async function collectDumpWithTool( toolName: string, fileExtension: string, userArgs: string, + dotnetPath: string, dumpFolder: string, channel: ObservableLogOutputChannel, filePrefix: string = 'csharp-lsp' @@ -311,12 +219,12 @@ export async function collectDumpWithTool( const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); const dumpFileName = `${filePrefix}-${timestamp}.${fileExtension}`; const dumpFilePath = path.join(dumpFolder, dumpFileName); - - const command = `${toolName} collect ${userArgs} --output "${dumpFilePath}"`; - channel.info(`Executing: ${command}`); + const args = [`dnx`, `--yes`, toolName, `collect`, userArgs, `--output`, dumpFilePath]; try { - const output = await execChildProcess(command, dumpFolder, process.env); + channel.info(`Executing: dotnet ${args.join(' ')}`); + + const output = await execFileChildProcess(dotnetPath, args, dumpFolder, process.env); channel.info(`${toolName} output: ${output}`); return fs.existsSync(dumpFilePath) ? dumpFilePath : undefined; @@ -336,9 +244,7 @@ export async function createZipWithLogs( outputChannel: ObservableLogOutputChannel, traceChannel: ObservableLogOutputChannel, razorLogger: RazorLogger, - csharpActivityLogContent: string, - traceActivityLogContent: string, - razorActivityLogContent: string, + activityLogs: ActivityLogResult | undefined, outputPath: string, traceFilePath?: string, additionalFiles?: string[] @@ -414,14 +320,10 @@ export async function createZipWithLogs( } // Add captured activity logs to the archive - if (csharpActivityLogContent !== '') { - archive.append(csharpActivityLogContent, { name: 'csharp.activity.log' }); - } - if (traceActivityLogContent !== '') { - archive.append(traceActivityLogContent, { name: 'csharp-lsp-trace.activity.log' }); - } - if (razorActivityLogContent !== '') { - archive.append(razorActivityLogContent, { name: 'razor.activity.log' }); + if (activityLogs) { + archive.append(activityLogs.csharpLog, { name: 'csharp.activity.log' }); + archive.append(activityLogs.lspTraceLog, { name: 'csharp-lsp-trace.activity.log' }); + archive.append(activityLogs.razorLog, { name: 'razor.activity.log' }); } // Add current settings to the archive @@ -607,3 +509,11 @@ export async function createActivityLogCapture( }, }; } + +/** Describes which additional logs were selected for collection. */ +export interface LogsToCollect { + activityLogs: boolean; + performanceTrace: boolean; + memoryDump: boolean; + gcDump: boolean; +} diff --git a/src/lsptoolshost/logging/profiling.ts b/src/lsptoolshost/logging/profiling.ts index 849bdd5b66..d137e4e9fe 100644 --- a/src/lsptoolshost/logging/profiling.ts +++ b/src/lsptoolshost/logging/profiling.ts @@ -3,25 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { EOL } from 'os'; -import { RoslynLanguageServer } from '../server/roslynLanguageServer'; -import { showErrorMessageWithOptions } from '../../shared/observers/utils/showMessage'; import { ObservableLogOutputChannel } from './observableLogOutputChannel'; -import { - verifyOrAcquireDotnetTool, - collectDumps, - createZipWithLogs, - getDefaultSaveUri, - selectDumpsWithArguments, - verifyDumpTools, - promptForToolArguments, - DumpRequest, - RazorLogObserver, -} from './loggingUtils'; -import { RazorLogger } from '../../razor/src/razorLogger'; const TraceTerminalName = 'dotnet-trace'; @@ -44,285 +29,20 @@ export function getProfilingEnvVars(outputChannel: vscode.LogOutputChannel): Nod return profilingEnvVars; } -interface TraceResults { - traceFilePath: string; - dumpFiles: string[]; - csharpLog: string; - lspLog: string; - razorLog: string; -} - -export function registerTraceCommand( - context: vscode.ExtensionContext, - languageServer: RoslynLanguageServer, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger -): void { - context.subscriptions.push( - vscode.commands.registerCommand('csharp.recordLanguageServerTrace', async () => { - const processId = RoslynLanguageServer.processId; - if (!processId) { - showErrorMessageWithOptions( - vscode, - vscode.l10n.t('Language server process not found, ensure the server is running.'), - { modal: true } - ); - return; - } - - // Step 1: Get trace arguments - const dotnetTraceArgs = `--process-id ${processId} --clreventlevel informational --providers "Microsoft-DotNETCore-SampleProfiler,Microsoft-Windows-DotNETRuntime,Microsoft-CodeAnalysis-General:0xFFFFFFFF:5,Microsoft-CodeAnalysis-Workspaces:0xFFFFFFFF:5,RoslynEventSource:0xFFFFFFFF:5"`; - const userArgs = await promptForToolArguments('dotnet-trace', dotnetTraceArgs); - if (userArgs === undefined) { - return; // User cancelled - } - - // Step 2: Ask about optional dumps and get arguments - const dumpRequests = await selectDumpsWithArguments({ - title: vscode.l10n.t('Capture Dumps With Trace'), - placeHolder: vscode.l10n.t('Optionally select dump(s) to capture before and after the trace'), - processId, - allowEmpty: true, - }); - if (dumpRequests === undefined) { - return; // User cancelled - } - - // Step 3: Ask for save location - const saveUri = await vscode.window.showSaveDialog({ - defaultUri: getDefaultSaveUri('csharp-trace'), - filters: { - // eslint-disable-next-line @typescript-eslint/naming-convention - 'Zip files': ['zip'], - }, - saveLabel: vscode.l10n.t('Save Trace and Logs'), - title: vscode.l10n.t('Save Trace and Logs'), - }); - if (!saveUri) { - return; // User cancelled - } - - const traceFolder = path.dirname(saveUri.fsPath); - if (!fs.existsSync(traceFolder)) { - showErrorMessageWithOptions( - vscode, - vscode.l10n.t({ - message: 'Folder for trace file {0} does not exist', - args: [traceFolder], - comment: ['{0} is the folder path'], - }), - { modal: true } - ); - return; - } - - // Step 4: Execute the trace with progress - let traceResults: TraceResults | undefined; - let resultUri: vscode.Uri | undefined; - let errorMessage: string | undefined; - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: 'dotnet-trace', - cancellable: true, - }, - async (progress, token) => { - try { - traceResults = await executeTraceCollection( - languageServer, - userArgs, - dumpRequests, - traceFolder, - progress, - outputChannel, - traceChannel, - razorLogger, - token - ); - } catch (error) { - errorMessage = error instanceof Error ? error.message : String(error); - } - } - ); - - if (errorMessage) { - showErrorMessageWithOptions( - vscode, - vscode.l10n.t({ - message: 'Failed to execute dotnet-trace command: {0}', - args: [errorMessage], - comment: 'dotnet-trace is a command name and should not be localized', - }), - { modal: true } - ); - return; - } - - if (!traceResults) { - return; - } - - // After the trace is stopped (via cancellation or natural completion), we need a new progress - // notification since the original one may have been dismissed by cancellation. - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: 'dotnet-trace', - cancellable: false, - }, - async (progress) => { - try { - resultUri = await saveTraceResults( - traceResults!, - context, - dumpRequests, - traceFolder, - saveUri, - progress, - outputChannel, - traceChannel, - razorLogger - ); - } catch (error) { - errorMessage = error instanceof Error ? error.message : String(error); - } - } - ); - - if (resultUri) { - const openFolder = vscode.l10n.t('Open Folder'); - const result = await vscode.window.showInformationMessage( - vscode.l10n.t('C# logs saved successfully.'), - openFolder - ); - if (result === openFolder) { - await vscode.commands.executeCommand('revealFileInOS', resultUri); - } - } - }) - ); -} - /** - * Executes the trace collection and archiving. + * Runs dotnet-trace in a VS Code terminal and returns the path to the .nettrace file. + * @param dotnetPath The path to the dotnet executable + * @param args The arguments to pass to `dotnet-trace collect` + * @param folder The working directory for the terminal + * @param outputChannel The output channel for logging + * @param token A cancellation token to stop the trace + * @returns The path to the generated .nettrace file, or undefined if not found */ -async function executeTraceCollection( - languageServer: RoslynLanguageServer, - userArgs: string, - dumpRequests: DumpRequest[], - traceFolder: string, - progress: vscode.Progress<{ message?: string; increment?: number }>, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger, - cancellationToken: vscode.CancellationToken -): Promise { - // Verify dotnet-trace is installed - progress.report({ - message: vscode.l10n.t({ - message: 'Verifying dotnet-trace...', - comment: 'dotnet-trace is a command name and should not be localized', - }), - }); - - const dotnetTraceInstalled = await verifyOrAcquireDotnetTool('dotnet-trace', traceFolder, progress, outputChannel); - if (!dotnetTraceInstalled) { - return undefined; - } - - const allDumpFiles: string[] = []; - - // Verify dump tools if needed - if (dumpRequests.length > 0) { - const toolsVerified = await verifyDumpTools(dumpRequests, traceFolder, progress, outputChannel); - if (!toolsVerified) { - throw new Error(vscode.l10n.t('Required dump tools could not be installed.')); - } - - const startDumps = await collectDumps(dumpRequests, traceFolder, progress, outputChannel, 'before-trace'); - allDumpFiles.push(...startDumps); - } - - const csharpLogObserver = outputChannel.observe(); - const traceLogObserver = traceChannel.observe(); - const razorLogObserver = new RazorLogObserver(razorLogger); - - // Set log levels to Trace for capture and get the restore function - const restoreLogLevels = await languageServer.setLogLevelsForCapture(); - razorLogger.traceEnabled = true; - razorLogger.debugEnabled = true; - razorLogger.infoEnabled = true; - - try { - const terminal = await getOrCreateTerminal(traceFolder, outputChannel); - - const args = ['collect', ...userArgs.split(' ')]; - - progress.report({ message: vscode.l10n.t('Recording logs... Click Cancel to stop and save.') }); - const traceFilePath = await runDotnetTrace(args, terminal, traceFolder, cancellationToken); - if (!traceFilePath) { - return undefined; - } - - return { - traceFilePath, - dumpFiles: allDumpFiles, - csharpLog: csharpLogObserver.getLog(), - lspLog: traceLogObserver.getLog(), - razorLog: razorLogObserver.getLog(), - }; - } finally { - // Always clean up observers and restore log levels - csharpLogObserver.dispose(); - traceLogObserver.dispose(); - await restoreLogLevels(); - await razorLogger.updateLogLevelAsync(); - } -} - -async function saveTraceResults( - traceResults: TraceResults, - context: vscode.ExtensionContext, - dumpRequests: DumpRequest[], - traceFolder: string, - saveUri: vscode.Uri, - progress: vscode.Progress<{ message?: string; increment?: number }>, - outputChannel: ObservableLogOutputChannel, - traceChannel: ObservableLogOutputChannel, - razorLogger: RazorLogger -): Promise { - // Collect dumps after trace if any selected - if (dumpRequests.length > 0) { - const endDumps = await collectDumps(dumpRequests, traceFolder, progress, outputChannel, 'after-trace'); - traceResults.dumpFiles.push(...endDumps); - } - - progress.report({ - message: vscode.l10n.t('Creating archive...'), - }); - - await createZipWithLogs( - context, - outputChannel, - traceChannel, - razorLogger, - traceResults.csharpLog, - traceResults.lspLog, - traceResults.razorLog, - saveUri.fsPath, - traceResults.traceFilePath, - traceResults.dumpFiles - ); - - return saveUri; -} - -async function runDotnetTrace( +export async function runDotnetTraceInTerminal( + dotnetPath: string, args: string[], - terminal: vscode.Terminal, - traceFolder: string, + folder: string, + outputChannel: ObservableLogOutputChannel, token: vscode.CancellationToken ): Promise { // Use a terminal to execute the dotnet-trace. This is much simpler and more reliable than executing dotnet-trace @@ -331,19 +51,36 @@ async function runDotnetTrace( // // Luckily, VSCode allows us to use the built in terminal (a psuedo-terminal) to execute commands, which also provides a way to send input to it. + const dotnetFolder = path.dirname(dotnetPath); + const dotnetExecutableName = path.basename(dotnetPath); + + // Generate an output file name in the form __.nettrace + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const outputFileName = `Microsoft.CodeAnalysis.LanguageServer_${timestamp}.nettrace`; + const outputFilePath = path.join(folder, outputFileName); + + // We create the terminal in the dotnet folder so that we can launch dotnet + // without having to quote the path which could create shell specific issues. + const terminal = await getOrCreateTerminal(dotnetFolder, outputChannel); terminal.show(); - const command = `dotnet-trace ${args.join(' ')}`; const shellIntegration = terminal.shellIntegration; + if (shellIntegration) { await new Promise((resolve, _) => { - const execution = shellIntegration.executeCommand(command); - + const execution = shellIntegration.executeCommand(dotnetPath, [ + 'dnx', + '--yes', + 'dotnet-trace', + 'collect', + '-o', + outputFilePath, + ...args, + ]); // If the progress is cancelled, we need to send a Ctrl+C to the terminal to stop the command. const cancelDisposable = token.onCancellationRequested(() => { terminal.sendText('^C'); }); - vscode.window.onDidEndTerminalShellExecution((e) => { if (e.execution === execution) { cancelDisposable.dispose(); // Clean up the cancellation listener. @@ -352,6 +89,11 @@ async function runDotnetTrace( }); }); } else { + // We use `./dotnet` since `dotnet` may use the system install instead of the local one. + const command = `.${ + path.sep + }${dotnetExecutableName} dnx --yes dotnet-trace collect -o ${outputFilePath} ${args.join(' ')}`; + // Without shell integration we can't listen for the command to finish. We can't execute it as a child process either (see above). // Instead we fire and forget the command. The user can stop the trace collection by interacting with the terminal directly. terminal.sendText(command); @@ -369,8 +111,7 @@ async function runDotnetTrace( }); } - // Find the most recent .nettrace file in the trace folder - return findLatestNettraceFile(traceFolder); + return outputFilePath; } async function getOrCreateTerminal( @@ -381,7 +122,6 @@ async function getOrCreateTerminal( if (existing) { const options: vscode.TerminalOptions = existing.creationOptions; if (options.cwd === folder) { - // If the terminal already exists and was created for the same folder, re-use it. return await waitForTerminalReady(existing, outputChannel); } } @@ -394,7 +134,6 @@ async function getOrCreateTerminal( }; const terminal = vscode.window.createTerminal(options); - return await waitForTerminalReady(terminal, outputChannel); } @@ -432,24 +171,3 @@ async function waitForTerminalReady( return terminal; } - -/** - * Finds the most recently created .nettrace file in the specified folder. - */ -function findLatestNettraceFile(folder: string): string | undefined { - try { - const files = fs.readdirSync(folder); - const nettraceFiles = files - .filter((f) => f.endsWith('.nettrace')) - .map((f) => { - const fullPath = path.join(folder, f); - const stats = fs.statSync(fullPath); - return { path: fullPath, mtime: stats.mtime }; - }) - .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); - - return nettraceFiles.length > 0 ? nettraceFiles[0].path : undefined; - } catch { - return undefined; - } -}