diff --git a/_extension/src/telemetryReporting.ts b/_extension/src/telemetryReporting.ts index 86d16ac6061..62ce5bd1d2f 100644 --- a/_extension/src/telemetryReporting.ts +++ b/_extension/src/telemetryReporting.ts @@ -62,6 +62,7 @@ export type LSErrorResponse = { errorCode: string; requestMethod: string; stack: string; + fileExtension?: string; }; export type EnableNativePreview = {}; diff --git a/internal/lsp/lsproto/_generate/generate.mts b/internal/lsp/lsproto/_generate/generate.mts index e01566d6f80..35c5d02e719 100755 --- a/internal/lsp/lsproto/_generate/generate.mts +++ b/internal/lsp/lsproto/_generate/generate.mts @@ -211,6 +211,12 @@ const customStructures: Structure[] = [ type: { kind: "base", name: "string" }, documentation: "The stack trace associated with the event.", }, + { + name: "fileExtension", + type: { kind: "base", name: "string" }, + optional: true, + documentation: "The file extension of the document that caused the event, if available.", + }, ], documentation: "RequestFailureTelemetryProperties contains failure information when an LSP request manages to recover.", }, diff --git a/internal/lsp/lsproto/lsp_generated.go b/internal/lsp/lsproto/lsp_generated.go index 6cfef994de3..f1aa618bb13 100644 --- a/internal/lsp/lsproto/lsp_generated.go +++ b/internal/lsp/lsproto/lsp_generated.go @@ -21964,6 +21964,9 @@ type RequestFailureTelemetryProperties struct { // The stack trace associated with the event. Stack string `json:"stack"` + + // The file extension of the document that caused the event, if available. + FileExtension *string `json:"fileExtension,omitzero"` } var _ json.UnmarshalerFrom = (*RequestFailureTelemetryProperties)(nil) @@ -22005,6 +22008,10 @@ func (s *RequestFailureTelemetryProperties) UnmarshalJSONFrom(dec *json.Decoder) if err := json.UnmarshalDecode(dec, &s.Stack); err != nil { return err } + case `"fileExtension"`: + if err := json.UnmarshalDecode(dec, &s.FileExtension); err != nil { + return err + } default: // Ignore unknown properties. } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 8519928c8a6..3fa40d06287 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -756,8 +756,9 @@ func registerLanguageServiceDocumentRequestHandler[Req lsproto.HasTextDocumentUR if err != nil { return nil, err } + fileExtension := tspath.TryGetExtensionFromPath(params.TextDocumentURI().FileName()) return func() error { - defer s.recover(req) + defer s.recover(req, fileExtension) resp, lsErr := fn(s, ctx, ls, params) if lsErr != nil { return lsErr @@ -781,8 +782,9 @@ func registerLanguageServiceWithAutoImportsRequestHandler[Req lsproto.HasTextDoc if err != nil { return nil, err } + fileExtension := tspath.TryGetExtensionFromPath(params.TextDocumentURI().FileName()) return func() error { - defer s.recover(req) + defer s.recover(req, fileExtension) resp, lsErr := fn(s, ctx, languageService, params) if errors.Is(lsErr, ls.ErrNeedsAutoImports) { languageService, lsErr = s.session.GetLanguageServiceWithAutoImports(ctx, snapshot, params.TextDocumentURI()) @@ -824,8 +826,9 @@ func registerMultiProjectReferenceRequestHandler[Req lsproto.HasTextDocumentPosi if err != nil { return nil, err } + fileExtension := tspath.TryGetExtensionFromPath(params.TextDocumentURI().FileName()) return func() error { - defer s.recover(req) + defer s.recover(req, fileExtension) resp, lsErr := fn(defaultLs, ctx, params, orchestrator) if lsErr != nil { return lsErr @@ -882,7 +885,7 @@ func (s *Server) getLanguageServiceAndCrossProjectOrchestrator(ctx context.Conte return defaultLs, orchestrator, err } -func (s *Server) recover(req *lsproto.RequestMessage) { +func (s *Server) recover(req *lsproto.RequestMessage, fileExtension string) { if r := recover(); r != nil { stack := debug.Stack() s.logger.Errorf("panic handling request %s: %v\n%s", req.Method, r, string(stack)) @@ -892,13 +895,17 @@ func (s *Server) recover(req *lsproto.RequestMessage) { return } + props := &lsproto.RequestFailureTelemetryProperties{ + ErrorCode: lsproto.ErrorCodeInternalError.String(), + RequestMethod: strings.ReplaceAll(string(req.Method), "/", "."), + Stack: sanitizeStackTrace(string(stack)), + } + if fileExtension != "" { + props.FileExtension = &fileExtension + } _ = sendNotification(s, lsproto.TelemetryEventInfo, lsproto.TelemetryEvent{ RequestFailureTelemetryEvent: &lsproto.RequestFailureTelemetryEvent{ - Properties: &lsproto.RequestFailureTelemetryProperties{ - ErrorCode: lsproto.ErrorCodeInternalError.String(), - RequestMethod: strings.ReplaceAll(string(req.Method), "/", "."), - Stack: sanitizeStackTrace(string(stack)), - }, + Properties: props, }, }) } else { @@ -1245,7 +1252,7 @@ func (s *Server) handleCompletionItemResolve(ctx context.Context, params *lsprot if err != nil { return nil, err } - defer s.recover(reqMsg) + defer s.recover(reqMsg, tspath.TryGetExtensionFromPath(data.FileName)) return languageService.ResolveCompletionItem( ctx, params, @@ -1282,7 +1289,7 @@ func (s *Server) handleDocumentOnTypeFormat(ctx context.Context, ls *ls.Language func (s *Server) handleWorkspaceSymbol(ctx context.Context, params *lsproto.WorkspaceSymbolParams, reqMsg *lsproto.RequestMessage) (lsproto.WorkspaceSymbolResponse, error) { snapshot := s.session.GetSnapshotLoadingProjectTree(ctx, nil) - defer s.recover(reqMsg) + defer s.recover(reqMsg, "") programs := core.Map(snapshot.ProjectCollection.Projects(), (*project.Project).GetProgram) return ls.ProvideWorkspaceSymbols( @@ -1336,7 +1343,7 @@ func (s *Server) handleCodeLensResolve(ctx context.Context, codeLens *lsproto.Co // based on non-existent files and line maps from shortened files. return codeLens, lsproto.ErrorCodeContentModified } - defer s.recover(reqMsg) + defer s.recover(reqMsg, tspath.TryGetExtensionFromPath(codeLens.Data.Uri.FileName())) return defaultLs.ResolveCodeLens( ctx, codeLens,