11import * as vscode from "vscode" ;
2+
23import {
4+ CloseAction ,
5+ CloseHandlerResult ,
6+ ErrorAction ,
7+ ErrorHandler ,
8+ ErrorHandlerResult ,
39 LanguageClient ,
410 LanguageClientOptions ,
11+ Message ,
512 NotebookDocumentFilter ,
613 ServerOptions ,
714 TextDocumentFilter ,
815 TransportKind ,
916} from "vscode-languageclient/node" ;
17+
1018import { codeLensShowLocationsCommandName } from "./commands" ;
1119import { registerTagClosingFeature } from "./languageFeatures/tagClosing" ;
20+ import * as tr from "./telemetryReporting" ;
1221import {
1322 ExeInfo ,
1423 getExe ,
@@ -19,6 +28,8 @@ import { getLanguageForUri } from "./util";
1928export class Client {
2029 private outputChannel : vscode . LogOutputChannel ;
2130 private traceOutputChannel : vscode . LogOutputChannel ;
31+ private telemetryReporter : tr . TelemetryReporter ;
32+
2233 private documentSelector : Array < { scheme : string ; language : string ; } > ;
2334 private clientOptions : LanguageClientOptions ;
2435 private client ?: LanguageClient ;
@@ -29,9 +40,10 @@ export class Client {
2940 private exe : ExeInfo | undefined ;
3041 private onStartedCallbacks : Set < ( ) => void > = new Set ( ) ;
3142
32- constructor ( outputChannel : vscode . LogOutputChannel , traceOutputChannel : vscode . LogOutputChannel ) {
43+ constructor ( outputChannel : vscode . LogOutputChannel , traceOutputChannel : vscode . LogOutputChannel , telemetryReporter : tr . TelemetryReporter ) {
3344 this . outputChannel = outputChannel ;
3445 this . traceOutputChannel = traceOutputChannel ;
46+ this . telemetryReporter = telemetryReporter ;
3547 this . documentSelector = [
3648 ...jsTsLanguageModes . map ( language => ( { scheme : "file" , language } ) ) ,
3749 ...jsTsLanguageModes . map ( language => ( { scheme : "untitled" , language } ) ) ,
@@ -43,6 +55,7 @@ export class Client {
4355 initializationOptions : {
4456 codeLensShowLocationsCommandName,
4557 } ,
58+ errorHandler : new ReportingErrorHandler ( this . telemetryReporter , 5 ) ,
4659 diagnosticPullOptions : {
4760 onChange : true ,
4861 onSave : true ,
@@ -100,6 +113,9 @@ export class Client {
100113 async start ( context : vscode . ExtensionContext , exe : { path : string ; version : string ; } ) : Promise < vscode . Disposable > {
101114 this . exe = exe ;
102115 this . outputChannel . appendLine ( `Resolved to ${ this . exe . path } ` ) ;
116+ this . telemetryReporter . sendTelemetryEvent ( "languageServer.start" , {
117+ version : this . exe . version ,
118+ } ) ;
103119
104120 // Get pprofDir
105121 const config = vscode . workspace . getConfiguration ( "typescript.native-preview" ) ;
@@ -150,7 +166,32 @@ export class Client {
150166 this . traceOutputChannel . appendLine ( `To see LSP trace output, set this output's log level to "Trace" (gear icon next to the dropdown).` ) ;
151167 }
152168
169+ type TelemetryData = {
170+ eventName : string ;
171+ telemetryPurpose : "usage" | "error" ;
172+ properties ?: Record < string , string > ;
173+ measurements ?: Record < string , number > ;
174+ } ;
175+
176+ const serverTelemetryListener = this . client . onTelemetry ( ( d : TelemetryData ) => {
177+ switch ( d . telemetryPurpose ) {
178+ case "usage" :
179+ this . telemetryReporter . sendTelemetryEventUntyped ( d . eventName , d . properties , d . measurements ) ;
180+ break ;
181+ case "error" :
182+ this . telemetryReporter . sendTelemetryErrorEventUntyped ( d . eventName , d . properties , d . measurements ) ;
183+ break ;
184+ default :
185+ const _ : never = d . telemetryPurpose ;
186+ this . telemetryReporter . sendTelemetryErrorEvent ( "languageServer.unexpectedTelemetryPurpose" , {
187+ telemetryPurpose : String ( d . telemetryPurpose ) ,
188+ } ) ;
189+ break ;
190+ }
191+ } ) ;
192+
153193 this . disposables . push (
194+ serverTelemetryListener ,
154195 registerTagClosingFeature ( "typescript" , this . documentSelector , this . client ) ,
155196 registerTagClosingFeature ( "javascript" , this . documentSelector , this . client ) ,
156197 ) ;
@@ -186,6 +227,7 @@ export class Client {
186227 }
187228
188229 this . onStartedCallbacks . add ( callback ) ;
230+
189231 return new vscode . Disposable ( ( ) => {
190232 this . onStartedCallbacks . delete ( callback ) ;
191233 } ) ;
@@ -247,3 +289,83 @@ export class Client {
247289 return result . file ;
248290 }
249291}
292+
293+ // Adapted from the default error handler in vscode-languageclient.
294+ class ReportingErrorHandler implements ErrorHandler {
295+ telemetryReporter : tr . TelemetryReporter ;
296+ maxRestartCount : number ;
297+ restarts : number [ ] ;
298+
299+ constructor ( telemetryReporter : tr . TelemetryReporter , maxRestartCount : number ) {
300+ this . telemetryReporter = telemetryReporter ;
301+ this . maxRestartCount = maxRestartCount ;
302+ this . restarts = [ ] ;
303+ }
304+
305+ error ( _error : Error , _message : Message | undefined , count : number | undefined ) : ErrorHandlerResult | Promise < ErrorHandlerResult > {
306+ let errorAction = ErrorAction . Shutdown ;
307+ if ( count && count <= 3 ) {
308+ errorAction = ErrorAction . Continue ;
309+ }
310+
311+ let actionString = "" ;
312+ switch ( errorAction ) {
313+ case ErrorAction . Continue :
314+ actionString = "continue" ;
315+ break ;
316+ case ErrorAction . Shutdown :
317+ actionString = "shutdown" ;
318+ break ;
319+ default :
320+ const _ : never = errorAction ;
321+ }
322+ this . telemetryReporter . sendTelemetryErrorEvent ( "languageServer.connectionError" , {
323+ resultingAction : actionString ,
324+ } ) ;
325+
326+ return { action : errorAction } ;
327+ }
328+
329+ closed ( ) : CloseHandlerResult | Promise < CloseHandlerResult > {
330+ let resultingAction : CloseAction ;
331+
332+ this . restarts . push ( Date . now ( ) ) ;
333+ if ( this . restarts . length <= this . maxRestartCount ) {
334+ resultingAction = CloseAction . Restart ;
335+ }
336+ else {
337+ const diff = this . restarts [ this . restarts . length - 1 ] - this . restarts [ 0 ] ;
338+ if ( diff <= 3 * 60 * 1000 ) {
339+ resultingAction = CloseAction . DoNotRestart ;
340+ }
341+ else {
342+ this . restarts . shift ( ) ;
343+ resultingAction = CloseAction . Restart ;
344+ }
345+ }
346+
347+ let actionString = "" ;
348+ switch ( resultingAction ) {
349+ case CloseAction . DoNotRestart :
350+ actionString = "doNotRestart" ;
351+ break ;
352+ case CloseAction . Restart :
353+ actionString = "restart" ;
354+ break ;
355+ default :
356+ const _ : never = resultingAction ;
357+ }
358+ this . telemetryReporter . sendTelemetryErrorEvent ( "languageServer.connectionClosed" , {
359+ resultingAction : actionString ,
360+ } ) ;
361+
362+ if ( resultingAction === CloseAction . DoNotRestart ) {
363+ return {
364+ action : resultingAction ,
365+ message : `The typescript.native-preview-lsp server crashed ${ this . maxRestartCount + 1 } times in the last 3 minutes. The server will not be restarted. See the output for more information.` ,
366+ } ;
367+ }
368+
369+ return { action : resultingAction } ;
370+ }
371+ }
0 commit comments