Skip to content

Commit ae7be39

Browse files
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 <[email protected]>
1 parent 88283a6 commit ae7be39

File tree

7 files changed

+167
-12
lines changed

7 files changed

+167
-12
lines changed

lldb/tools/lldb-dap/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lldb/tools/lldb-dap/package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,16 @@
8181
"description": "The path to the lldb-dap binary, e.g. /usr/local/bin/lldb-dap"
8282
},
8383
"lldb-dap.log-path": {
84+
"scope": "machine-overridable",
85+
"type": "string",
86+
"description": "The log path for lldb-dap (if any)",
87+
"markdownDeprecationMessage": "Use the `#lldb-dap.logFolder#` setting instead"
88+
},
89+
"lldb-dap.logFolder": {
8490
"order": 0,
8591
"scope": "machine-overridable",
8692
"type": "string",
87-
"description": "The log path for lldb-dap (if any)"
93+
"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)."
8894
},
8995
"lldb-dap.serverMode": {
9096
"order": 0,
@@ -110,6 +116,11 @@
110116
"additionalProperties": {
111117
"type": "string"
112118
}
119+
},
120+
"lldb-dap.captureSessionLogs": {
121+
"type": "boolean",
122+
"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.",
123+
"default": false
113124
}
114125
}
115126
},

lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as child_process from "child_process";
55
import * as fs from "node:fs/promises";
66
import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message";
77
import { ErrorWithNotification } from "./ui/error-with-notification";
8+
import { LogFilePathProvider, LogType } from "./logging";
89

910
const exec = util.promisify(child_process.execFile);
1011

@@ -160,12 +161,16 @@ async function getDAPArguments(
160161
* Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and
161162
* debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.
162163
*
164+
* @param logger The {@link vscode.LogOutputChannel} to log setup diagnostics
165+
* @param logFilePath The {@link LogFilePathProvider} for determining where to put session logs
163166
* @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within
164167
* @param configuration The {@link vscode.DebugConfiguration} that will be launched
165168
* @throws An {@link ErrorWithNotification} if something went wrong
166169
* @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap
167170
*/
168171
export async function createDebugAdapterExecutable(
172+
logger: vscode.LogOutputChannel,
173+
logFilePath: LogFilePathProvider,
169174
workspaceFolder: vscode.WorkspaceFolder | undefined,
170175
configuration: vscode.DebugConfiguration,
171176
): Promise<vscode.DebugAdapterExecutable> {
@@ -176,6 +181,10 @@ export async function createDebugAdapterExecutable(
176181
let env: { [key: string]: string } = {};
177182
if (log_path) {
178183
env["LLDBDAP_LOG"] = log_path;
184+
} else if (
185+
vscode.workspace.getConfiguration("lldb-dap").get("captureSessionLogs", false)
186+
) {
187+
env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION);
179188
}
180189
const configEnvironment =
181190
config.get<{ [key: string]: string }>("environment") || {};
@@ -190,6 +199,11 @@ export async function createDebugAdapterExecutable(
190199
};
191200
const dbgArgs = await getDAPArguments(workspaceFolder, configuration);
192201

202+
logger.info(`lldb-dap path: ${dapPath}`);
203+
logger.info(`lldb-dap args: ${dbgArgs}`);
204+
logger.info(`cwd: ${dbgOptions.cwd}`);
205+
logger.info(`env: ${JSON.stringify(dbgOptions.env)}`);
206+
193207
return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);
194208
}
195209

@@ -200,25 +214,42 @@ export async function createDebugAdapterExecutable(
200214
export class LLDBDapDescriptorFactory
201215
implements vscode.DebugAdapterDescriptorFactory
202216
{
217+
constructor(
218+
private readonly logger: vscode.LogOutputChannel,
219+
private logFilePath: LogFilePathProvider,
220+
) {}
221+
203222
async createDebugAdapterDescriptor(
204223
session: vscode.DebugSession,
205224
executable: vscode.DebugAdapterExecutable | undefined,
206225
): Promise<vscode.DebugAdapterDescriptor | undefined> {
226+
this.logger.info(`Creating debug adapter for session "${session.name}"`);
227+
this.logger.info(
228+
`Session "${session.name}" debug configuration:\n` +
229+
JSON.stringify(session.configuration, undefined, 2),
230+
);
207231
if (executable) {
208-
throw new Error(
232+
const error = new Error(
209233
"Setting the debug adapter executable in the package.json is not supported.",
210234
);
235+
this.logger.error(error);
236+
throw error;
211237
}
212238

213239
// Use a server connection if the debugAdapterPort is provided
214240
if (session.configuration.debugAdapterPort) {
241+
this.logger.info(
242+
`Spawning debug adapter server on port ${session.configuration.debugAdapterPort}`,
243+
);
215244
return new vscode.DebugAdapterServer(
216245
session.configuration.debugAdapterPort,
217246
session.configuration.debugAdapterHostname,
218247
);
219248
}
220249

221250
return createDebugAdapterExecutable(
251+
this.logger,
252+
this.logFilePath,
222253
session.workspaceFolder,
223254
session.configuration,
224255
);

lldb/tools/lldb-dap/src-ts/debug-configuration-provider.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { LLDBDapServer } from "./lldb-dap-server";
55
import { createDebugAdapterExecutable } from "./debug-adapter-factory";
66
import { ConfigureButton, showErrorMessage } from "./ui/show-error-message";
77
import { ErrorWithNotification } from "./ui/error-with-notification";
8+
import { LogFilePathProvider } from "./logging";
89

910
const exec = util.promisify(child_process.execFile);
1011

@@ -71,13 +72,24 @@ const configurations: Record<string, DefaultConfig> = {
7172
export class LLDBDapConfigurationProvider
7273
implements vscode.DebugConfigurationProvider
7374
{
74-
constructor(private readonly server: LLDBDapServer) {}
75+
constructor(
76+
private readonly server: LLDBDapServer,
77+
private readonly logger: vscode.LogOutputChannel,
78+
private readonly logFilePath: LogFilePathProvider,
79+
) {}
7580

7681
async resolveDebugConfiguration(
7782
folder: vscode.WorkspaceFolder | undefined,
7883
debugConfiguration: vscode.DebugConfiguration,
7984
token?: vscode.CancellationToken,
8085
): Promise<vscode.DebugConfiguration> {
86+
this.logger.info(
87+
`Resolving debug configuration for "${debugConfiguration.name}"`,
88+
);
89+
this.logger.debug(
90+
"Initial debug configuration:\n" +
91+
JSON.stringify(debugConfiguration, undefined, 2),
92+
);
8193
let config = vscode.workspace.getConfiguration("lldb-dap");
8294
for (const [key, cfg] of Object.entries(configurations)) {
8395
if (Reflect.has(debugConfiguration, key)) {
@@ -152,6 +164,8 @@ export class LLDBDapConfigurationProvider
152164
// Always try to create the debug adapter executable as this will show the user errors
153165
// if there are any.
154166
const executable = await createDebugAdapterExecutable(
167+
this.logger,
168+
this.logFilePath,
155169
folder,
156170
debugConfiguration,
157171
);
@@ -184,8 +198,14 @@ export class LLDBDapConfigurationProvider
184198
}
185199
}
186200

201+
this.logger.info(
202+
"Resolved debug configuration:\n" +
203+
JSON.stringify(debugConfiguration, undefined, 2),
204+
);
205+
187206
return debugConfiguration;
188207
} catch (error) {
208+
this.logger.error(error as Error);
189209
// Show a better error message to the user if possible
190210
if (!(error instanceof ErrorWithNotification)) {
191211
throw error;

lldb/tools/lldb-dap/src-ts/debug-session-tracker.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as vscode from "vscode";
55
// prettier-ignore
66
interface EventMap {
77
"module": DebugProtocol.ModuleEvent;
8+
"exited": DebugProtocol.ExitedEvent;
89
}
910

1011
/** 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
4748
onDidChangeModules: vscode.Event<vscode.DebugSession | undefined> =
4849
this.modulesChanged.event;
4950

50-
constructor() {
51+
constructor(private logger: vscode.LogOutputChannel) {
5152
this.onDidChangeModules(this.moduleChangedListener, this);
5253
vscode.debug.onDidChangeActiveDebugSession((session) =>
5354
this.modulesChanged.fire(session),
@@ -62,8 +63,12 @@ export class DebugSessionTracker
6263
createDebugAdapterTracker(
6364
session: vscode.DebugSession,
6465
): vscode.ProviderResult<vscode.DebugAdapterTracker> {
66+
this.logger.info(`Starting debug session "${session.name}"`);
67+
let stopping = false;
6568
return {
69+
onError: (error) => !stopping && this.logger.error(error), // Can throw benign read errors when shutting down.
6670
onDidSendMessage: (message) => this.onDidSendMessage(session, message),
71+
onWillStopSession: () => (stopping = true),
6772
onExit: () => this.onExit(session),
6873
};
6974
}
@@ -134,6 +139,13 @@ export class DebugSessionTracker
134139
}
135140
this.modules.set(session, modules);
136141
this.modulesChanged.fire(session);
142+
} else if (isEvent(message, "exited")) {
143+
// The vscode.DebugAdapterTracker#onExit event is sometimes called with
144+
// exitCode = undefined but the exit event from LLDB-DAP always has the "exitCode"
145+
const { exitCode } = message.body;
146+
this.logger.info(
147+
`Session "${session.name}" exited with code ${exitCode}`,
148+
);
137149
}
138150
}
139151
}

lldb/tools/lldb-dap/src-ts/extension.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as path from "path";
12
import * as vscode from "vscode";
23

34
import { LLDBDapDescriptorFactory } from "./debug-adapter-factory";
@@ -10,28 +11,35 @@ import {
1011
ModulesDataProvider,
1112
ModuleProperty,
1213
} from "./ui/modules-data-provider";
14+
import { LogFilePathProvider } from "./logging";
1315

1416
/**
1517
* This class represents the extension and manages its life cycle. Other extensions
1618
* using it as as library should use this class as the main entry point.
1719
*/
1820
export class LLDBDapExtension extends DisposableContext {
19-
constructor() {
21+
constructor(
22+
logger: vscode.LogOutputChannel,
23+
logFilePath: LogFilePathProvider,
24+
outputChannel: vscode.OutputChannel,
25+
) {
2026
super();
2127

2228
const lldbDapServer = new LLDBDapServer();
23-
const sessionTracker = new DebugSessionTracker();
29+
const sessionTracker = new DebugSessionTracker(logger);
2430

2531
this.pushSubscription(
32+
logger,
33+
outputChannel,
2634
lldbDapServer,
2735
sessionTracker,
2836
vscode.debug.registerDebugConfigurationProvider(
2937
"lldb-dap",
30-
new LLDBDapConfigurationProvider(lldbDapServer),
38+
new LLDBDapConfigurationProvider(lldbDapServer, logger, logFilePath),
3139
),
3240
vscode.debug.registerDebugAdapterDescriptorFactory(
3341
"lldb-dap",
34-
new LLDBDapDescriptorFactory(),
42+
new LLDBDapDescriptorFactory(logger, logFilePath),
3543
),
3644
vscode.debug.registerDebugAdapterTrackerFactory(
3745
"lldb-dap",
@@ -54,6 +62,12 @@ export class LLDBDapExtension extends DisposableContext {
5462
/**
5563
* This is the entry point when initialized by VS Code.
5664
*/
57-
export function activate(context: vscode.ExtensionContext) {
58-
context.subscriptions.push(new LLDBDapExtension());
65+
export async function activate(context: vscode.ExtensionContext) {
66+
const outputChannel = vscode.window.createOutputChannel("LLDB-DAP", { log: true });
67+
outputChannel.info("LLDB-DAP extension activating...");
68+
const logFilePath = new LogFilePathProvider(context, outputChannel);
69+
context.subscriptions.push(
70+
new LLDBDapExtension(outputChannel, logFilePath, outputChannel),
71+
);
72+
outputChannel.info("LLDB-DAP extension activated");
5973
}

lldb/tools/lldb-dap/src-ts/logging.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as path from "path";
2+
import * as vscode from "vscode";
3+
4+
/**
5+
* Formats the given date as a string in the form "YYYYMMddTHHMMSS".
6+
*
7+
* @param date The date to format as a string.
8+
* @returns The formatted date.
9+
*/
10+
function formatDate(date: Date): string {
11+
const year = date.getFullYear().toString().padStart(4, "0");
12+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
13+
const day = date.getDate().toString().padStart(2, "0");
14+
const hour = date.getHours().toString().padStart(2, "0");
15+
const minute = date.getMinutes().toString().padStart(2, "0");
16+
const seconds = date.getSeconds().toString().padStart(2, "0");
17+
return `${year}${month}${day}T${hour}${minute}${seconds}`;
18+
}
19+
20+
export enum LogType {
21+
DEBUG_SESSION,
22+
}
23+
24+
export class LogFilePathProvider {
25+
private logFolder: string = "";
26+
27+
constructor(
28+
private context: vscode.ExtensionContext,
29+
private logger: vscode.LogOutputChannel,
30+
) {
31+
this.updateLogFolder();
32+
context.subscriptions.push(
33+
vscode.workspace.onDidChangeConfiguration(e => {
34+
if (
35+
e.affectsConfiguration("lldb-dap.logFolder")
36+
) {
37+
this.updateLogFolder();
38+
}
39+
})
40+
);
41+
}
42+
43+
get(type: LogType): string {
44+
const logFolder = this.logFolder || this.context.logUri.fsPath;
45+
switch(type) {
46+
case LogType.DEBUG_SESSION:
47+
return path.join(logFolder, `lldb-dap-session-${formatDate(new Date())}.log`);
48+
break;
49+
}
50+
}
51+
52+
private updateLogFolder() {
53+
const config = vscode.workspace.getConfiguration("lldb-dap");
54+
let logFolder =
55+
config.get<string>("logFolder") || this.context.logUri.fsPath;
56+
vscode.workspace.fs
57+
.createDirectory(vscode.Uri.file(logFolder))
58+
.then(undefined, (error) => {
59+
this.logger.error(`Failed to create log folder ${logFolder}: ${error}`);
60+
logFolder = this.context.logUri.fsPath;
61+
})
62+
.then(() => {
63+
this.logFolder = logFolder;
64+
this.logger.info(`Persisting lldb-dap logs to ${logFolder}`);
65+
});
66+
}
67+
}

0 commit comments

Comments
 (0)