-
Notifications
You must be signed in to change notification settings - Fork 511
/
Copy pathlogging.ts
232 lines (198 loc) · 8.97 KB
/
logging.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import utils = require("./utils");
import os = require("os");
import vscode = require("vscode");
// NOTE: This is not a string enum because the order is used for comparison.
export enum LogLevel {
Diagnostic,
Verbose,
Normal,
Warning,
Error,
None,
}
/** Interface for logging operations. New features should use this interface for the "type" of logger.
* This will allow for easy mocking of the logger during unit tests.
*/
export interface ILogger {
getLogFilePath(baseName: string): vscode.Uri;
updateLogLevel(logLevelName: string): void;
write(message: string, ...additionalMessages: string[]): void;
writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise<void>;
writeDiagnostic(message: string, ...additionalMessages: string[]): void;
writeVerbose(message: string, ...additionalMessages: string[]): void;
writeWarning(message: string, ...additionalMessages: string[]): void;
writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise<void>;
writeError(message: string, ...additionalMessages: string[]): void;
writeAndShowError(message: string, ...additionalMessages: string[]): Promise<void>;
writeAndShowErrorWithActions(
message: string,
actions: { prompt: string; action: (() => Promise<void>) | undefined }[]): Promise<void>;
}
export class Logger implements ILogger {
public logDirectoryPath: vscode.Uri;
private logLevel: LogLevel;
private commands: vscode.Disposable[];
private logChannel: vscode.OutputChannel;
private logFilePath: vscode.Uri;
private logDirectoryCreated = false;
private writingLog = false;
constructor(logLevelName: string, globalStorageUri: vscode.Uri) {
this.logLevel = Logger.logLevelNameToValue(logLevelName);
this.logChannel = vscode.window.createOutputChannel("PowerShell Extension Logs");
// We have to override the scheme because it defaults to
// 'vscode-userdata' which breaks UNC paths.
this.logDirectoryPath = vscode.Uri.joinPath(
globalStorageUri.with({ scheme: "file" }),
"logs",
`${Math.floor(Date.now() / 1000)}-${vscode.env.sessionId}`);
this.logFilePath = this.getLogFilePath("vscode-powershell");
// Early logging of the log paths for debugging.
if (LogLevel.Diagnostic >= this.logLevel) {
const uriMessage = Logger.timestampMessage(`Log file path: '${this.logFilePath}'`, LogLevel.Verbose);
this.logChannel.appendLine(uriMessage);
}
this.commands = [
vscode.commands.registerCommand(
"PowerShell.ShowLogs",
() => { this.showLogPanel(); }),
vscode.commands.registerCommand(
"PowerShell.OpenLogFolder",
async () => { await this.openLogFolder(); }),
];
}
public dispose(): void {
this.logChannel.dispose();
for (const command of this.commands) {
command.dispose();
}
}
public getLogFilePath(baseName: string): vscode.Uri {
return vscode.Uri.joinPath(this.logDirectoryPath, `${baseName}.log`);
}
private writeAtLevel(logLevel: LogLevel, message: string, ...additionalMessages: string[]): void {
if (logLevel >= this.logLevel) {
void this.writeLine(message, logLevel);
for (const additionalMessage of additionalMessages) {
void this.writeLine(additionalMessage, logLevel);
}
}
}
public write(message: string, ...additionalMessages: string[]): void {
this.writeAtLevel(LogLevel.Normal, message, ...additionalMessages);
}
public async writeAndShowInformation(message: string, ...additionalMessages: string[]): Promise<void> {
this.write(message, ...additionalMessages);
const selection = await vscode.window.showInformationMessage(message, "Show Logs", "Okay");
if (selection === "Show Logs") {
this.showLogPanel();
}
}
public writeDiagnostic(message: string, ...additionalMessages: string[]): void {
this.writeAtLevel(LogLevel.Diagnostic, message, ...additionalMessages);
}
public writeVerbose(message: string, ...additionalMessages: string[]): void {
this.writeAtLevel(LogLevel.Verbose, message, ...additionalMessages);
}
public writeWarning(message: string, ...additionalMessages: string[]): void {
this.writeAtLevel(LogLevel.Warning, message, ...additionalMessages);
}
public async writeAndShowWarning(message: string, ...additionalMessages: string[]): Promise<void> {
this.writeWarning(message, ...additionalMessages);
const selection = await vscode.window.showWarningMessage(message, "Show Logs");
if (selection !== undefined) {
this.showLogPanel();
}
}
public writeError(message: string, ...additionalMessages: string[]): void {
this.writeAtLevel(LogLevel.Error, message, ...additionalMessages);
}
public async writeAndShowError(message: string, ...additionalMessages: string[]): Promise<void> {
this.writeError(message, ...additionalMessages);
const choice = await vscode.window.showErrorMessage(message, "Show Logs");
if (choice !== undefined) {
this.showLogPanel();
}
}
public async writeAndShowErrorWithActions(
message: string,
actions: { prompt: string; action: (() => Promise<void>) | undefined }[]): Promise<void> {
this.writeError(message);
const fullActions = [
...actions,
{ prompt: "Show Logs", action: (): void => { this.showLogPanel(); } },
];
const actionKeys: string[] = fullActions.map((action) => action.prompt);
const choice = await vscode.window.showErrorMessage(message, ...actionKeys);
if (choice) {
for (const action of fullActions) {
if (choice === action.prompt && action.action !== undefined ) {
await action.action();
return;
}
}
}
}
// TODO: Make the enum smarter about strings so this goes away.
private static logLevelNameToValue(logLevelName: string): LogLevel {
switch (logLevelName.trim().toLowerCase()) {
case "diagnostic": return LogLevel.Diagnostic;
case "verbose": return LogLevel.Verbose;
case "normal": return LogLevel.Normal;
case "warning": return LogLevel.Warning;
case "error": return LogLevel.Error;
case "none": return LogLevel.None;
default: return LogLevel.Normal;
}
}
public updateLogLevel(logLevelName: string): void {
this.logLevel = Logger.logLevelNameToValue(logLevelName);
}
private showLogPanel(): void {
this.logChannel.show();
}
private async openLogFolder(): Promise<void> {
if (this.logDirectoryCreated) {
// Open the folder in VS Code since there isn't an easy way to
// open the folder in the platform's file browser
await vscode.commands.executeCommand("vscode.openFolder", this.logDirectoryPath, true);
} else {
void this.writeAndShowError("Cannot open PowerShell log directory as it does not exist!");
}
}
private static timestampMessage(message: string, level: LogLevel): string {
const now = new Date();
return `${now.toLocaleDateString()} ${now.toLocaleTimeString()} [${LogLevel[level].toUpperCase()}] - ${message}${os.EOL}`;
}
// TODO: Should we await this function above?
private async writeLine(message: string, level: LogLevel = LogLevel.Normal): Promise<void> {
const timestampedMessage = Logger.timestampMessage(message, level);
this.logChannel.appendLine(timestampedMessage);
if (this.logLevel !== LogLevel.None) {
// A simple lock because this function isn't re-entrant.
while (this.writingLog) {
await utils.sleep(300);
}
try {
this.writingLog = true;
if (!this.logDirectoryCreated) {
this.writeVerbose(`Creating log directory at: '${this.logDirectoryPath}'`);
await vscode.workspace.fs.createDirectory(this.logDirectoryPath);
this.logDirectoryCreated = true;
}
let log = new Uint8Array();
if (await utils.checkIfFileExists(this.logFilePath)) {
log = await vscode.workspace.fs.readFile(this.logFilePath);
}
await vscode.workspace.fs.writeFile(
this.logFilePath,
Buffer.concat([log, Buffer.from(timestampedMessage)]));
} catch (err) {
console.log(`Error writing to vscode-powershell log file: ${err}`);
} finally {
this.writingLog = false;
}
}
}
}