[Lldb-commits] [lldb] ae7be39 - Logging setup for lldb-dap extension (#146884)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Aug 5 08:14:59 PDT 2025
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 <jonas at 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}`);
+ });
+ }
+}
More information about the lldb-commits
mailing list