Author: award999 Date: 2025-08-05T08:14:55-07:00 New Revision: ae7be39601496aa8f712672844de82285a227646
URL: https://github.com/llvm/llvm-project/commit/ae7be39601496aa8f712672844de82285a227646 DIFF: https://github.com/llvm/llvm-project/commit/ae7be39601496aa8f712672844de82285a227646.diff LOG: Logging setup for lldb-dap extension (#146884) - ~Add `winston` dependency (MIT license) to handle logging setup~ - Have an `LogOutputChannel` to log user facing information, errors, warnings - Write a debug session logs under the provided `logUri` to capture further diagnostics when the `lldb-dap.captureSessionLogs` setting is enabled. *Note* the `lldb-dap.log-path` setting takes precedence when set Issue: #146880 --------- Co-authored-by: Jonas Devlieghere <jo...@devlieghere.com> Added: lldb/tools/lldb-dap/src-ts/logging.ts Modified: lldb/tools/lldb-dap/package-lock.json lldb/tools/lldb-dap/package.json lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts lldb/tools/lldb-dap/src-ts/extension.ts Removed: ################################################################################ diff --git a/lldb/tools/lldb-dap/package-lock.json b/lldb/tools/lldb-dap/package-lock.json index af90a9573aee6..1969b196accc6 100644 --- a/lldb/tools/lldb-dap/package-lock.json +++ b/lldb/tools/lldb-dap/package-lock.json @@ -1,12 +1,12 @@ { "name": "lldb-dap", - "version": "0.2.14", + "version": "0.2.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lldb-dap", - "version": "0.2.14", + "version": "0.2.15", "license": "Apache 2.0 License with LLVM exceptions", "devDependencies": { "@types/node": "^18.19.41", diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 801abe73edd7d..fa2d4daffaf27 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -81,10 +81,16 @@ "description": "The path to the lldb-dap binary, e.g. /usr/local/bin/lldb-dap" }, "lldb-dap.log-path": { + "scope": "machine-overridable", + "type": "string", + "description": "The log path for lldb-dap (if any)", + "markdownDeprecationMessage": "Use the `#lldb-dap.logFolder#` setting instead" + }, + "lldb-dap.logFolder": { "order": 0, "scope": "machine-overridable", "type": "string", - "description": "The log path for lldb-dap (if any)" + "markdownDescription": "The folder to persist lldb-dap logs. If no value is provided, logs will be persisted in the [Extension Logs Folder](command:workbench.action.openExtensionLogsFolder)." }, "lldb-dap.serverMode": { "order": 0, @@ -110,6 +116,11 @@ "additionalProperties": { "type": "string" } + }, + "lldb-dap.captureSessionLogs": { + "type": "boolean", + "description": "When enabled, LLDB-DAP session logs will be written to the Extension's log folder if the `lldb-dap.log-path` setting is not explicitly set.", + "default": false } } }, diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts index 015bcf77ddd29..6e94400b09155 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts @@ -5,6 +5,7 @@ import * as child_process from "child_process"; import * as fs from "node:fs/promises"; import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message"; import { ErrorWithNotification } from "./ui/error-with-notification"; +import { LogFilePathProvider, LogType } from "./logging"; const exec = util.promisify(child_process.execFile); @@ -160,12 +161,16 @@ async function getDAPArguments( * Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and * debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap. * + * @param logger The {@link vscode.LogOutputChannel} to log setup diagnostics + * @param logFilePath The {@link LogFilePathProvider} for determining where to put session logs * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within * @param configuration The {@link vscode.DebugConfiguration} that will be launched * @throws An {@link ErrorWithNotification} if something went wrong * @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap */ export async function createDebugAdapterExecutable( + logger: vscode.LogOutputChannel, + logFilePath: LogFilePathProvider, workspaceFolder: vscode.WorkspaceFolder | undefined, configuration: vscode.DebugConfiguration, ): Promise<vscode.DebugAdapterExecutable> { @@ -176,6 +181,10 @@ export async function createDebugAdapterExecutable( let env: { [key: string]: string } = {}; if (log_path) { env["LLDBDAP_LOG"] = log_path; + } else if ( + vscode.workspace.getConfiguration("lldb-dap").get("captureSessionLogs", false) + ) { + env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION); } const configEnvironment = config.get<{ [key: string]: string }>("environment") || {}; @@ -190,6 +199,11 @@ export async function createDebugAdapterExecutable( }; const dbgArgs = await getDAPArguments(workspaceFolder, configuration); + logger.info(`lldb-dap path: ${dapPath}`); + logger.info(`lldb-dap args: ${dbgArgs}`); + logger.info(`cwd: ${dbgOptions.cwd}`); + logger.info(`env: ${JSON.stringify(dbgOptions.env)}`); + return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions); } @@ -200,18 +214,33 @@ export async function createDebugAdapterExecutable( export class LLDBDapDescriptorFactory implements vscode.DebugAdapterDescriptorFactory { + constructor( + private readonly logger: vscode.LogOutputChannel, + private logFilePath: LogFilePathProvider, + ) {} + async createDebugAdapterDescriptor( session: vscode.DebugSession, executable: vscode.DebugAdapterExecutable | undefined, ): Promise<vscode.DebugAdapterDescriptor | undefined> { + this.logger.info(`Creating debug adapter for session "${session.name}"`); + this.logger.info( + `Session "${session.name}" debug configuration:\n` + + JSON.stringify(session.configuration, undefined, 2), + ); if (executable) { - throw new Error( + const error = new Error( "Setting the debug adapter executable in the package.json is not supported.", ); + this.logger.error(error); + throw error; } // Use a server connection if the debugAdapterPort is provided if (session.configuration.debugAdapterPort) { + this.logger.info( + `Spawning debug adapter server on port ${session.configuration.debugAdapterPort}`, + ); return new vscode.DebugAdapterServer( session.configuration.debugAdapterPort, session.configuration.debugAdapterHostname, @@ -219,6 +248,8 @@ export class LLDBDapDescriptorFactory } return createDebugAdapterExecutable( + this.logger, + this.logFilePath, session.workspaceFolder, session.configuration, ); diff --git a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts index 316ffaf47c3d2..8c04ec2bdc9d3 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts @@ -5,6 +5,7 @@ import { LLDBDapServer } from "./lldb-dap-server"; import { createDebugAdapterExecutable } from "./debug-adapter-factory"; import { ConfigureButton, showErrorMessage } from "./ui/show-error-message"; import { ErrorWithNotification } from "./ui/error-with-notification"; +import { LogFilePathProvider } from "./logging"; const exec = util.promisify(child_process.execFile); @@ -71,13 +72,24 @@ const configurations: Record<string, DefaultConfig> = { export class LLDBDapConfigurationProvider implements vscode.DebugConfigurationProvider { - constructor(private readonly server: LLDBDapServer) {} + constructor( + private readonly server: LLDBDapServer, + private readonly logger: vscode.LogOutputChannel, + private readonly logFilePath: LogFilePathProvider, + ) {} async resolveDebugConfiguration( folder: vscode.WorkspaceFolder | undefined, debugConfiguration: vscode.DebugConfiguration, token?: vscode.CancellationToken, ): Promise<vscode.DebugConfiguration> { + this.logger.info( + `Resolving debug configuration for "${debugConfiguration.name}"`, + ); + this.logger.debug( + "Initial debug configuration:\n" + + JSON.stringify(debugConfiguration, undefined, 2), + ); let config = vscode.workspace.getConfiguration("lldb-dap"); for (const [key, cfg] of Object.entries(configurations)) { if (Reflect.has(debugConfiguration, key)) { @@ -152,6 +164,8 @@ export class LLDBDapConfigurationProvider // Always try to create the debug adapter executable as this will show the user errors // if there are any. const executable = await createDebugAdapterExecutable( + this.logger, + this.logFilePath, folder, debugConfiguration, ); @@ -184,8 +198,14 @@ export class LLDBDapConfigurationProvider } } + this.logger.info( + "Resolved debug configuration:\n" + + JSON.stringify(debugConfiguration, undefined, 2), + ); + return debugConfiguration; } catch (error) { + this.logger.error(error as Error); // Show a better error message to the user if possible if (!(error instanceof ErrorWithNotification)) { throw error; diff --git a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts index 50db1e1c3a7b0..7d7f73dbff92d 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode"; // prettier-ignore interface EventMap { "module": DebugProtocol.ModuleEvent; + "exited": DebugProtocol.ExitedEvent; } /** A type assertion to check if a ProtocolMessage is an event or if it is a specific event. */ @@ -47,7 +48,7 @@ export class DebugSessionTracker onDidChangeModules: vscode.Event<vscode.DebugSession | undefined> = this.modulesChanged.event; - constructor() { + constructor(private logger: vscode.LogOutputChannel) { this.onDidChangeModules(this.moduleChangedListener, this); vscode.debug.onDidChangeActiveDebugSession((session) => this.modulesChanged.fire(session), @@ -62,8 +63,12 @@ export class DebugSessionTracker createDebugAdapterTracker( session: vscode.DebugSession, ): vscode.ProviderResult<vscode.DebugAdapterTracker> { + this.logger.info(`Starting debug session "${session.name}"`); + let stopping = false; return { + onError: (error) => !stopping && this.logger.error(error), // Can throw benign read errors when shutting down. onDidSendMessage: (message) => this.onDidSendMessage(session, message), + onWillStopSession: () => (stopping = true), onExit: () => this.onExit(session), }; } @@ -134,6 +139,13 @@ export class DebugSessionTracker } this.modules.set(session, modules); this.modulesChanged.fire(session); + } else if (isEvent(message, "exited")) { + // The vscode.DebugAdapterTracker#onExit event is sometimes called with + // exitCode = undefined but the exit event from LLDB-DAP always has the "exitCode" + const { exitCode } = message.body; + this.logger.info( + `Session "${session.name}" exited with code ${exitCode}`, + ); } } } diff --git a/lldb/tools/lldb-dap/src-ts/extension.ts b/lldb/tools/lldb-dap/src-ts/extension.ts index c8e5146e29cea..4b7a35e6944c6 100644 --- a/lldb/tools/lldb-dap/src-ts/extension.ts +++ b/lldb/tools/lldb-dap/src-ts/extension.ts @@ -1,3 +1,4 @@ +import * as path from "path"; import * as vscode from "vscode"; import { LLDBDapDescriptorFactory } from "./debug-adapter-factory"; @@ -10,28 +11,35 @@ import { ModulesDataProvider, ModuleProperty, } from "./ui/modules-data-provider"; +import { LogFilePathProvider } from "./logging"; /** * This class represents the extension and manages its life cycle. Other extensions * using it as as library should use this class as the main entry point. */ export class LLDBDapExtension extends DisposableContext { - constructor() { + constructor( + logger: vscode.LogOutputChannel, + logFilePath: LogFilePathProvider, + outputChannel: vscode.OutputChannel, + ) { super(); const lldbDapServer = new LLDBDapServer(); - const sessionTracker = new DebugSessionTracker(); + const sessionTracker = new DebugSessionTracker(logger); this.pushSubscription( + logger, + outputChannel, lldbDapServer, sessionTracker, vscode.debug.registerDebugConfigurationProvider( "lldb-dap", - new LLDBDapConfigurationProvider(lldbDapServer), + new LLDBDapConfigurationProvider(lldbDapServer, logger, logFilePath), ), vscode.debug.registerDebugAdapterDescriptorFactory( "lldb-dap", - new LLDBDapDescriptorFactory(), + new LLDBDapDescriptorFactory(logger, logFilePath), ), vscode.debug.registerDebugAdapterTrackerFactory( "lldb-dap", @@ -54,6 +62,12 @@ export class LLDBDapExtension extends DisposableContext { /** * This is the entry point when initialized by VS Code. */ -export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push(new LLDBDapExtension()); +export async function activate(context: vscode.ExtensionContext) { + const outputChannel = vscode.window.createOutputChannel("LLDB-DAP", { log: true }); + outputChannel.info("LLDB-DAP extension activating..."); + const logFilePath = new LogFilePathProvider(context, outputChannel); + context.subscriptions.push( + new LLDBDapExtension(outputChannel, logFilePath, outputChannel), + ); + outputChannel.info("LLDB-DAP extension activated"); } diff --git a/lldb/tools/lldb-dap/src-ts/logging.ts b/lldb/tools/lldb-dap/src-ts/logging.ts new file mode 100644 index 0000000000000..3b1c3c37ce1ce --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/logging.ts @@ -0,0 +1,67 @@ +import * as path from "path"; +import * as vscode from "vscode"; + +/** + * Formats the given date as a string in the form "YYYYMMddTHHMMSS". + * + * @param date The date to format as a string. + * @returns The formatted date. + */ +function formatDate(date: Date): string { + const year = date.getFullYear().toString().padStart(4, "0"); + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + const hour = date.getHours().toString().padStart(2, "0"); + const minute = date.getMinutes().toString().padStart(2, "0"); + const seconds = date.getSeconds().toString().padStart(2, "0"); + return `${year}${month}${day}T${hour}${minute}${seconds}`; +} + +export enum LogType { + DEBUG_SESSION, +} + +export class LogFilePathProvider { + private logFolder: string = ""; + + constructor( + private context: vscode.ExtensionContext, + private logger: vscode.LogOutputChannel, + ) { + this.updateLogFolder(); + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(e => { + if ( + e.affectsConfiguration("lldb-dap.logFolder") + ) { + this.updateLogFolder(); + } + }) + ); + } + + get(type: LogType): string { + const logFolder = this.logFolder || this.context.logUri.fsPath; + switch(type) { + case LogType.DEBUG_SESSION: + return path.join(logFolder, `lldb-dap-session-${formatDate(new Date())}.log`); + break; + } + } + + private updateLogFolder() { + const config = vscode.workspace.getConfiguration("lldb-dap"); + let logFolder = + config.get<string>("logFolder") || this.context.logUri.fsPath; + vscode.workspace.fs + .createDirectory(vscode.Uri.file(logFolder)) + .then(undefined, (error) => { + this.logger.error(`Failed to create log folder ${logFolder}: ${error}`); + logFolder = this.context.logUri.fsPath; + }) + .then(() => { + this.logFolder = logFolder; + this.logger.info(`Persisting lldb-dap logs to ${logFolder}`); + }); + } +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits