/
analyzer_status_reporter.ts
142 lines (118 loc) · 4.76 KB
/
analyzer_status_reporter.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
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { env, ProgressLocation, version as codeVersion, window, workspace } from "vscode";
import { RequestError, ServerErrorNotification, ServerStatusNotification } from "../../shared/analysis_server_types";
import { LogCategory } from "../../shared/enums";
import { Logger } from "../../shared/interfaces";
import { PromiseCompleter } from "../../shared/utils";
import { getRandomInt } from "../../shared/utils/fs";
import { extensionVersion } from "../../shared/vscode/extension_utils";
import { WorkspaceContext } from "../../shared/workspace";
import { Analytics } from "../analytics";
import { config } from "../config";
import { getSdkVersion } from "../utils";
import { DasAnalyzerClient } from "./analyzer_das";
const maxErrorReportCount = 3;
const sendFakeErrorAtStartup = false;
let errorCount = 0;
export class AnalyzerStatusReporter {
private analysisInProgress = false;
private analyzingPromise?: PromiseCompleter<void>;
constructor(private readonly logger: Logger, private readonly analyzer: DasAnalyzerClient, private readonly workspaceContext: WorkspaceContext, private readonly analytics: Analytics) {
// TODO: Should these go in disposables?
// If so, do we need to worry about server cleaning them up if it disposes first?
analyzer.registerForServerStatus((n) => this.handleServerStatus(n));
analyzer.registerForServerError((e) => this.handleServerError(e));
analyzer.registerForRequestError((e) => this.handleRequestError(e));
if (sendFakeErrorAtStartup) {
setTimeout(() => {
this.handleServerError(
{
isFatal: false,
message: "This is a fake error for testing the error reporting!",
stackTrace: new Error().stack || "",
},
"testError",
);
}, 5000);
}
}
private handleServerStatus(status: ServerStatusNotification) {
if (!status.analysis)
return;
this.analysisInProgress = status.analysis.isAnalyzing;
if (this.analysisInProgress) {
// Debounce short analysis times.
setTimeout(() => {
// When the timeout fires, we need to check analysisInProgress again in case
// analysis has already finished.
if (this.analysisInProgress && !this.analyzingPromise) {
window.withProgress({ location: ProgressLocation.Window, title: "Analyzing…" }, (_) => {
if (!this.analyzingPromise) // Re-check, since we don't know how long before this callback is called.
this.analyzingPromise = new PromiseCompleter();
return this.analyzingPromise.promise;
});
}
}, 500);
} else {
if (this.analyzingPromise) {
this.analyzingPromise.resolve();
this.analyzingPromise = undefined;
}
}
}
private handleRequestError(error: RequestError & { method?: string }) {
// Map this request error to a server error to reuse the shared code.
this.handleServerError(
{
isFatal: false,
message: error.message,
stackTrace: error.stackTrace || "",
},
error.method,
);
}
private handleServerError(error: ServerErrorNotification, method?: string) {
// Always log to the console.
this.logger.error(error.message, LogCategory.Analyzer);
if (error.stackTrace)
this.logger.error(error.stackTrace, LogCategory.Analyzer);
this.analytics.logError(`Analyzer server error${method ? ` (${method})` : ""}`, error.isFatal);
errorCount++;
// Offer to report the error.
if (config.notifyAnalyzerErrors && errorCount <= maxErrorReportCount) {
const showLog: string = "Show log";
window.showErrorMessage(`Exception from the Dart analysis server: ${error.message}`, showLog).then((res) => {
if (res === showLog)
this.showErrorLog(error, method);
});
}
}
private showErrorLog(error: ServerErrorNotification, method?: string) {
const sdkVersion = getSdkVersion(this.logger, this.workspaceContext.sdks.dart);
const flutterSdkVersion = this.workspaceContext.sdks.dartSdkIsFromFlutter
? getSdkVersion(this.logger, this.workspaceContext.sdks.flutter)
: undefined;
const analyzerArgs = this.analyzer.getAnalyzerLaunchArgs();
const data = `
${method ? "### Request\n\nServer was responding to request: `" + method + "`\n" : ""}
### Versions
- ${env.appName} v${codeVersion}
- Dart Code v${extensionVersion}
- ${flutterSdkVersion ? `Flutter SDK v${flutterSdkVersion}` : `Dart SDK v${sdkVersion}`}
### Analyzer Info
The analyzer was launched using the arguments:
${analyzerArgs.map((a) => `- ${a}`).join("\n")}
### Exception${error.isFatal ? " (fatal)" : ""}
${error.message}
${error.stackTrace.trim()}
`;
const fileName = `bug-${getRandomInt(0x1000, 0x10000).toString(16)}.md`;
const tempPath = path.join(os.tmpdir(), fileName);
fs.writeFileSync(tempPath, data.trim());
workspace.openTextDocument(tempPath).then((document) => {
window.showTextDocument(document);
});
}
}