Version 2.15.0-290.0.dev

Merge commit 'b0bca6ae6a0cff71ee763c32ce5924f84a40346e' into 'dev'
diff --git a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
index 2e4f847..4a20a40 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/js_util_optimizer.dart
@@ -139,11 +139,10 @@
           VariableGet(function.positionalParameters.first),
           StringLiteral(_getExtensionMemberName(node))
         ], types: [
-          DynamicType()
+          function.returnType
         ]))
       ..fileOffset = node.fileOffset;
-    return ReturnStatement(
-        AsExpression(getPropertyInvocation, function.returnType));
+    return ReturnStatement(getPropertyInvocation);
   }
 
   /// Returns a new function body for the given [node] external setter.
@@ -153,18 +152,18 @@
   ReturnStatement _getExternalSetterBody(Procedure node) {
     var function = node.function;
     assert(function.positionalParameters.length == 2);
+    var value = function.positionalParameters.last;
     var setPropertyInvocation = StaticInvocation(
         _setPropertyTarget,
         Arguments([
           VariableGet(function.positionalParameters.first),
           StringLiteral(_getExtensionMemberName(node)),
-          VariableGet(function.positionalParameters.last)
+          VariableGet(value)
         ], types: [
-          DynamicType()
+          value.type
         ]))
       ..fileOffset = node.fileOffset;
-    return ReturnStatement(AsExpression(
-        _lowerSetProperty(setPropertyInvocation), function.returnType));
+    return ReturnStatement(_lowerSetProperty(setPropertyInvocation));
   }
 
   /// Returns a new function body for the given [node] external method.
@@ -183,11 +182,10 @@
               .map((argument) => VariableGet(argument))
               .toList())
         ], types: [
-          DynamicType()
+          function.returnType
         ]))
       ..fileOffset = node.fileOffset;
-    return ReturnStatement(AsExpression(
-        _lowerCallMethod(callMethodInvocation), function.returnType));
+    return ReturnStatement(_lowerCallMethod(callMethodInvocation));
   }
 
   /// Returns the extension member name.
diff --git a/pkg/analysis_server/doc/api.html b/pkg/analysis_server/doc/api.html
index 09adf9a..b84e3fd 100644
--- a/pkg/analysis_server/doc/api.html
+++ b/pkg/analysis_server/doc/api.html
@@ -420,6 +420,7 @@
   
   
   
+  
 <h3>Requests</h3><dl><dt class="request"><a name="request_server.getVersion">server.getVersion</a></dt><dd><div class="box"><pre>request: {
   "id": String
   "method": "server.getVersion"
@@ -3081,6 +3082,8 @@
   
   
   
+  
+  
 <dl><dt class="typeDefinition"><a name="type_AddContentOverlay">AddContentOverlay: object</a></dt><dd>
     <p>
       A directive to begin overlaying the contents of a file. The supplied
@@ -5566,7 +5569,7 @@
       An enumeration of the services provided by the server domain.
     </p>
     
-  <dl><dt class="value">STATUS</dt></dl></dd><dt class="typeDefinition"><a name="type_SourceChange">SourceChange: object</a></dt><dd>
+  <dl><dt class="value">LOG</dt><dt class="value">STATUS</dt></dl></dd><dt class="typeDefinition"><a name="type_SourceChange">SourceChange: object</a></dt><dd>
     <p>
       A description of a set of edits that implement a single conceptual change.
     </p>
@@ -6062,7 +6065,7 @@
   TODO: TBD
 </p>
 <h2 class="domain"><a name="index">Index</a></h2>
-<h3>Domains</h3><h4>server (<a href="#domain_server">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_server.getVersion">getVersion</a></li><li><a href="#request_server.shutdown">shutdown</a></li><li><a href="#request_server.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_server.connected">connected</a></li><li><a href="#notification_server.error">error</a></li><li><a href="#notification_server.status">status</a></li></ul></div></div><h4>analysis (<a href="#domain_analysis">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_analysis.getErrors">getErrors</a></li><li><a href="#request_analysis.getHover">getHover</a></li><li><a href="#request_analysis.getLibraryDependencies">getLibraryDependencies</a></li><li><a href="#request_analysis.getNavigation">getNavigation</a></li><li><a href="#request_analysis.getReachableSources">getReachableSources</a></li><li><a href="#request_analysis.reanalyze">reanalyze</a></li><li><a href="#request_analysis.setAnalysisRoots">setAnalysisRoots</a></li><li><a href="#request_analysis.setGeneralSubscriptions">setGeneralSubscriptions</a></li><li><a href="#request_analysis.setPriorityFiles">setPriorityFiles</a></li><li><a href="#request_analysis.setSubscriptions">setSubscriptions</a></li><li><a href="#request_analysis.updateContent">updateContent</a></li><li><a href="#request_analysis.updateOptions">updateOptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_analysis.analyzedFiles">analyzedFiles</a></li><li><a href="#notification_analysis.closingLabels">closingLabels</a></li><li><a href="#notification_analysis.errors">errors</a></li><li><a href="#notification_analysis.flushResults">flushResults</a></li><li><a href="#notification_analysis.folding">folding</a></li><li><a href="#notification_analysis.highlights">highlights</a></li><li><a href="#notification_analysis.implemented">implemented</a></li><li><a href="#notification_analysis.invalidate">invalidate</a></li><li><a href="#notification_analysis.navigation">navigation</a></li><li><a href="#notification_analysis.occurrences">occurrences</a></li><li><a href="#notification_analysis.outline">outline</a></li><li><a href="#notification_analysis.overrides">overrides</a></li></ul></div></div><h4>completion (<a href="#domain_completion">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_completion.getSuggestions">getSuggestions</a></li><li><a href="#request_completion.setSubscriptions">setSubscriptions</a></li><li><a href="#request_completion.registerLibraryPaths">registerLibraryPaths</a></li><li><a href="#request_completion.getSuggestionDetails">getSuggestionDetails</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_completion.results">results</a></li><li><a href="#notification_completion.availableSuggestions">availableSuggestions</a></li><li><a href="#notification_completion.existingImports">existingImports</a></li></ul></div></div><h4>search (<a href="#domain_search">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_search.findElementReferences">findElementReferences</a></li><li><a href="#request_search.findMemberDeclarations">findMemberDeclarations</a></li><li><a href="#request_search.findMemberReferences">findMemberReferences</a></li><li><a href="#request_search.findTopLevelDeclarations">findTopLevelDeclarations</a></li><li><a href="#request_search.getTypeHierarchy">getTypeHierarchy</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_search.results">results</a></li></ul></div></div><h4>edit (<a href="#domain_edit">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_edit.format">format</a></li><li><a href="#request_edit.getAssists">getAssists</a></li><li><a href="#request_edit.getAvailableRefactorings">getAvailableRefactorings</a></li><li><a href="#request_edit.getFixes">getFixes</a></li><li><a href="#request_edit.getPostfixCompletion">getPostfixCompletion</a></li><li><a href="#request_edit.getRefactoring">getRefactoring</a></li><li><a href="#request_edit.sortMembers">sortMembers</a></li><li><a href="#request_edit.organizeDirectives">organizeDirectives</a></li></ul></div><h4>execution (<a href="#domain_execution">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_execution.createContext">createContext</a></li><li><a href="#request_execution.deleteContext">deleteContext</a></li><li><a href="#request_execution.getSuggestions">getSuggestions</a></li><li><a href="#request_execution.mapUri">mapUri</a></li><li><a href="#request_execution.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_execution.launchData">launchData</a></li></ul></div></div><h4>diagnostic (<a href="#domain_diagnostic">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_diagnostic.getDiagnostics">getDiagnostics</a></li><li><a href="#request_diagnostic.getServerPort">getServerPort</a></li></ul></div><h4>flutter (<a href="#domain_flutter">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_flutter.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_flutter.outline">outline</a></li></ul></div></div><h3>Types (<a href="#types">↑</a>)</h3><div class="subindex"><ul><li><a href="#type_AddContentOverlay">AddContentOverlay</a></li><li><a href="#type_AnalysisError">AnalysisError</a></li><li><a href="#type_AnalysisErrorFixes">AnalysisErrorFixes</a></li><li><a href="#type_AnalysisErrorSeverity">AnalysisErrorSeverity</a></li><li><a href="#type_AnalysisErrorType">AnalysisErrorType</a></li><li><a href="#type_AnalysisOptions">AnalysisOptions</a></li><li><a href="#type_AnalysisService">AnalysisService</a></li><li><a href="#type_AnalysisStatus">AnalysisStatus</a></li><li><a href="#type_AvailableSuggestion">AvailableSuggestion</a></li><li><a href="#type_AvailableSuggestionRelevanceTag">AvailableSuggestionRelevanceTag</a></li><li><a href="#type_AvailableSuggestionSet">AvailableSuggestionSet</a></li><li><a href="#type_BulkFix">BulkFix</a></li><li><a href="#type_BulkFixDetail">BulkFixDetail</a></li><li><a href="#type_ChangeContentOverlay">ChangeContentOverlay</a></li><li><a href="#type_ClosingLabel">ClosingLabel</a></li><li><a href="#type_CompletionId">CompletionId</a></li><li><a href="#type_CompletionService">CompletionService</a></li><li><a href="#type_CompletionSuggestion">CompletionSuggestion</a></li><li><a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a></li><li><a href="#type_ContextData">ContextData</a></li><li><a href="#type_DiagnosticMessage">DiagnosticMessage</a></li><li><a href="#type_Element">Element</a></li><li><a href="#type_ElementDeclaration">ElementDeclaration</a></li><li><a href="#type_ElementKind">ElementKind</a></li><li><a href="#type_ExecutableFile">ExecutableFile</a></li><li><a href="#type_ExecutableKind">ExecutableKind</a></li><li><a href="#type_ExecutionContextId">ExecutionContextId</a></li><li><a href="#type_ExecutionService">ExecutionService</a></li><li><a href="#type_ExistingImport">ExistingImport</a></li><li><a href="#type_ExistingImports">ExistingImports</a></li><li><a href="#type_FileKind">FileKind</a></li><li><a href="#type_FilePath">FilePath</a></li><li><a href="#type_FlutterOutline">FlutterOutline</a></li><li><a href="#type_FlutterOutlineAttribute">FlutterOutlineAttribute</a></li><li><a href="#type_FlutterOutlineKind">FlutterOutlineKind</a></li><li><a href="#type_FlutterService">FlutterService</a></li><li><a href="#type_FlutterWidgetProperty">FlutterWidgetProperty</a></li><li><a href="#type_FlutterWidgetPropertyEditor">FlutterWidgetPropertyEditor</a></li><li><a href="#type_FlutterWidgetPropertyEditorKind">FlutterWidgetPropertyEditorKind</a></li><li><a href="#type_FlutterWidgetPropertyValue">FlutterWidgetPropertyValue</a></li><li><a href="#type_FlutterWidgetPropertyValueEnumItem">FlutterWidgetPropertyValueEnumItem</a></li><li><a href="#type_FoldingKind">FoldingKind</a></li><li><a href="#type_FoldingRegion">FoldingRegion</a></li><li><a href="#type_GeneralAnalysisService">GeneralAnalysisService</a></li><li><a href="#type_HighlightRegion">HighlightRegion</a></li><li><a href="#type_HighlightRegionType">HighlightRegionType</a></li><li><a href="#type_HoverInformation">HoverInformation</a></li><li><a href="#type_ImplementedClass">ImplementedClass</a></li><li><a href="#type_ImplementedMember">ImplementedMember</a></li><li><a href="#type_ImportedElementSet">ImportedElementSet</a></li><li><a href="#type_ImportedElements">ImportedElements</a></li><li><a href="#type_IncludedSuggestionRelevanceTag">IncludedSuggestionRelevanceTag</a></li><li><a href="#type_IncludedSuggestionSet">IncludedSuggestionSet</a></li><li><a href="#type_KytheEntry">KytheEntry</a></li><li><a href="#type_KytheVName">KytheVName</a></li><li><a href="#type_LibraryPathSet">LibraryPathSet</a></li><li><a href="#type_LinkedEditGroup">LinkedEditGroup</a></li><li><a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a></li><li><a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a></li><li><a href="#type_Location">Location</a></li><li><a href="#type_NavigationRegion">NavigationRegion</a></li><li><a href="#type_NavigationTarget">NavigationTarget</a></li><li><a href="#type_Occurrences">Occurrences</a></li><li><a href="#type_Outline">Outline</a></li><li><a href="#type_OverriddenMember">OverriddenMember</a></li><li><a href="#type_Override">Override</a></li><li><a href="#type_Position">Position</a></li><li><a href="#type_PostfixTemplateDescriptor">PostfixTemplateDescriptor</a></li><li><a href="#type_PubStatus">PubStatus</a></li><li><a href="#type_RefactoringFeedback">RefactoringFeedback</a></li><li><a href="#type_RefactoringKind">RefactoringKind</a></li><li><a href="#type_RefactoringMethodParameter">RefactoringMethodParameter</a></li><li><a href="#type_RefactoringMethodParameterKind">RefactoringMethodParameterKind</a></li><li><a href="#type_RefactoringOptions">RefactoringOptions</a></li><li><a href="#type_RefactoringProblem">RefactoringProblem</a></li><li><a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a></li><li><a href="#type_RemoveContentOverlay">RemoveContentOverlay</a></li><li><a href="#type_RequestError">RequestError</a></li><li><a href="#type_RequestErrorCode">RequestErrorCode</a></li><li><a href="#type_RuntimeCompletionExpression">RuntimeCompletionExpression</a></li><li><a href="#type_RuntimeCompletionExpressionType">RuntimeCompletionExpressionType</a></li><li><a href="#type_RuntimeCompletionExpressionTypeKind">RuntimeCompletionExpressionTypeKind</a></li><li><a href="#type_RuntimeCompletionVariable">RuntimeCompletionVariable</a></li><li><a href="#type_SearchId">SearchId</a></li><li><a href="#type_SearchResult">SearchResult</a></li><li><a href="#type_SearchResultKind">SearchResultKind</a></li><li><a href="#type_ServerService">ServerService</a></li><li><a href="#type_SourceChange">SourceChange</a></li><li><a href="#type_SourceEdit">SourceEdit</a></li><li><a href="#type_SourceFileEdit">SourceFileEdit</a></li><li><a href="#type_TypeHierarchyItem">TypeHierarchyItem</a></li></ul></div><h3>Refactorings (<a href="#refactorings">↑</a>)</h3><div class="subindex"><ul><li><a href="#refactoring_CONVERT_GETTER_TO_METHOD">CONVERT_GETTER_TO_METHOD</a></li><li><a href="#refactoring_CONVERT_METHOD_TO_GETTER">CONVERT_METHOD_TO_GETTER</a></li><li><a href="#refactoring_EXTRACT_LOCAL_VARIABLE">EXTRACT_LOCAL_VARIABLE</a></li><li><a href="#refactoring_EXTRACT_METHOD">EXTRACT_METHOD</a></li><li><a href="#refactoring_EXTRACT_WIDGET">EXTRACT_WIDGET</a></li><li><a href="#refactoring_INLINE_LOCAL_VARIABLE">INLINE_LOCAL_VARIABLE</a></li><li><a href="#refactoring_INLINE_METHOD">INLINE_METHOD</a></li><li><a href="#refactoring_MOVE_FILE">MOVE_FILE</a></li><li><a href="#refactoring_RENAME">RENAME</a></li></ul></div>
+<h3>Domains</h3><h4>server (<a href="#domain_server">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_server.getVersion">getVersion</a></li><li><a href="#request_server.shutdown">shutdown</a></li><li><a href="#request_server.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_server.connected">connected</a></li><li><a href="#notification_server.error">error</a></li><li><a href="#notification_server.log">log</a></li><li><a href="#notification_server.status">status</a></li></ul></div></div><h4>analysis (<a href="#domain_analysis">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_analysis.getErrors">getErrors</a></li><li><a href="#request_analysis.getHover">getHover</a></li><li><a href="#request_analysis.getLibraryDependencies">getLibraryDependencies</a></li><li><a href="#request_analysis.getNavigation">getNavigation</a></li><li><a href="#request_analysis.getReachableSources">getReachableSources</a></li><li><a href="#request_analysis.reanalyze">reanalyze</a></li><li><a href="#request_analysis.setAnalysisRoots">setAnalysisRoots</a></li><li><a href="#request_analysis.setGeneralSubscriptions">setGeneralSubscriptions</a></li><li><a href="#request_analysis.setPriorityFiles">setPriorityFiles</a></li><li><a href="#request_analysis.setSubscriptions">setSubscriptions</a></li><li><a href="#request_analysis.updateContent">updateContent</a></li><li><a href="#request_analysis.updateOptions">updateOptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_analysis.analyzedFiles">analyzedFiles</a></li><li><a href="#notification_analysis.closingLabels">closingLabels</a></li><li><a href="#notification_analysis.errors">errors</a></li><li><a href="#notification_analysis.flushResults">flushResults</a></li><li><a href="#notification_analysis.folding">folding</a></li><li><a href="#notification_analysis.highlights">highlights</a></li><li><a href="#notification_analysis.implemented">implemented</a></li><li><a href="#notification_analysis.invalidate">invalidate</a></li><li><a href="#notification_analysis.navigation">navigation</a></li><li><a href="#notification_analysis.occurrences">occurrences</a></li><li><a href="#notification_analysis.outline">outline</a></li><li><a href="#notification_analysis.overrides">overrides</a></li></ul></div></div><h4>completion (<a href="#domain_completion">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_completion.getSuggestions">getSuggestions</a></li><li><a href="#request_completion.setSubscriptions">setSubscriptions</a></li><li><a href="#request_completion.registerLibraryPaths">registerLibraryPaths</a></li><li><a href="#request_completion.getSuggestionDetails">getSuggestionDetails</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_completion.results">results</a></li><li><a href="#notification_completion.availableSuggestions">availableSuggestions</a></li><li><a href="#notification_completion.existingImports">existingImports</a></li></ul></div></div><h4>search (<a href="#domain_search">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_search.findElementReferences">findElementReferences</a></li><li><a href="#request_search.findMemberDeclarations">findMemberDeclarations</a></li><li><a href="#request_search.findMemberReferences">findMemberReferences</a></li><li><a href="#request_search.findTopLevelDeclarations">findTopLevelDeclarations</a></li><li><a href="#request_search.getTypeHierarchy">getTypeHierarchy</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_search.results">results</a></li></ul></div></div><h4>edit (<a href="#domain_edit">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_edit.format">format</a></li><li><a href="#request_edit.getAssists">getAssists</a></li><li><a href="#request_edit.getAvailableRefactorings">getAvailableRefactorings</a></li><li><a href="#request_edit.getFixes">getFixes</a></li><li><a href="#request_edit.getPostfixCompletion">getPostfixCompletion</a></li><li><a href="#request_edit.getRefactoring">getRefactoring</a></li><li><a href="#request_edit.sortMembers">sortMembers</a></li><li><a href="#request_edit.organizeDirectives">organizeDirectives</a></li></ul></div><h4>execution (<a href="#domain_execution">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_execution.createContext">createContext</a></li><li><a href="#request_execution.deleteContext">deleteContext</a></li><li><a href="#request_execution.getSuggestions">getSuggestions</a></li><li><a href="#request_execution.mapUri">mapUri</a></li><li><a href="#request_execution.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_execution.launchData">launchData</a></li></ul></div></div><h4>diagnostic (<a href="#domain_diagnostic">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_diagnostic.getDiagnostics">getDiagnostics</a></li><li><a href="#request_diagnostic.getServerPort">getServerPort</a></li></ul></div><h4>flutter (<a href="#domain_flutter">↑</a>)</h4><div class="subindex"><h5>Requests</h5><ul><li><a href="#request_flutter.setSubscriptions">setSubscriptions</a></li></ul><h5>Notifications</h5><div class="subindex"><ul><li><a href="#notification_flutter.outline">outline</a></li></ul></div></div><h3>Types (<a href="#types">↑</a>)</h3><div class="subindex"><ul><li><a href="#type_AddContentOverlay">AddContentOverlay</a></li><li><a href="#type_AnalysisError">AnalysisError</a></li><li><a href="#type_AnalysisErrorFixes">AnalysisErrorFixes</a></li><li><a href="#type_AnalysisErrorSeverity">AnalysisErrorSeverity</a></li><li><a href="#type_AnalysisErrorType">AnalysisErrorType</a></li><li><a href="#type_AnalysisOptions">AnalysisOptions</a></li><li><a href="#type_AnalysisService">AnalysisService</a></li><li><a href="#type_AnalysisStatus">AnalysisStatus</a></li><li><a href="#type_AvailableSuggestion">AvailableSuggestion</a></li><li><a href="#type_AvailableSuggestionRelevanceTag">AvailableSuggestionRelevanceTag</a></li><li><a href="#type_AvailableSuggestionSet">AvailableSuggestionSet</a></li><li><a href="#type_BulkFix">BulkFix</a></li><li><a href="#type_BulkFixDetail">BulkFixDetail</a></li><li><a href="#type_ChangeContentOverlay">ChangeContentOverlay</a></li><li><a href="#type_ClosingLabel">ClosingLabel</a></li><li><a href="#type_CompletionId">CompletionId</a></li><li><a href="#type_CompletionService">CompletionService</a></li><li><a href="#type_CompletionSuggestion">CompletionSuggestion</a></li><li><a href="#type_CompletionSuggestionKind">CompletionSuggestionKind</a></li><li><a href="#type_ContextData">ContextData</a></li><li><a href="#type_DiagnosticMessage">DiagnosticMessage</a></li><li><a href="#type_Element">Element</a></li><li><a href="#type_ElementDeclaration">ElementDeclaration</a></li><li><a href="#type_ElementKind">ElementKind</a></li><li><a href="#type_ExecutableFile">ExecutableFile</a></li><li><a href="#type_ExecutableKind">ExecutableKind</a></li><li><a href="#type_ExecutionContextId">ExecutionContextId</a></li><li><a href="#type_ExecutionService">ExecutionService</a></li><li><a href="#type_ExistingImport">ExistingImport</a></li><li><a href="#type_ExistingImports">ExistingImports</a></li><li><a href="#type_FileKind">FileKind</a></li><li><a href="#type_FilePath">FilePath</a></li><li><a href="#type_FlutterOutline">FlutterOutline</a></li><li><a href="#type_FlutterOutlineAttribute">FlutterOutlineAttribute</a></li><li><a href="#type_FlutterOutlineKind">FlutterOutlineKind</a></li><li><a href="#type_FlutterService">FlutterService</a></li><li><a href="#type_FlutterWidgetProperty">FlutterWidgetProperty</a></li><li><a href="#type_FlutterWidgetPropertyEditor">FlutterWidgetPropertyEditor</a></li><li><a href="#type_FlutterWidgetPropertyEditorKind">FlutterWidgetPropertyEditorKind</a></li><li><a href="#type_FlutterWidgetPropertyValue">FlutterWidgetPropertyValue</a></li><li><a href="#type_FlutterWidgetPropertyValueEnumItem">FlutterWidgetPropertyValueEnumItem</a></li><li><a href="#type_FoldingKind">FoldingKind</a></li><li><a href="#type_FoldingRegion">FoldingRegion</a></li><li><a href="#type_GeneralAnalysisService">GeneralAnalysisService</a></li><li><a href="#type_HighlightRegion">HighlightRegion</a></li><li><a href="#type_HighlightRegionType">HighlightRegionType</a></li><li><a href="#type_HoverInformation">HoverInformation</a></li><li><a href="#type_ImplementedClass">ImplementedClass</a></li><li><a href="#type_ImplementedMember">ImplementedMember</a></li><li><a href="#type_ImportedElementSet">ImportedElementSet</a></li><li><a href="#type_ImportedElements">ImportedElements</a></li><li><a href="#type_IncludedSuggestionRelevanceTag">IncludedSuggestionRelevanceTag</a></li><li><a href="#type_IncludedSuggestionSet">IncludedSuggestionSet</a></li><li><a href="#type_KytheEntry">KytheEntry</a></li><li><a href="#type_KytheVName">KytheVName</a></li><li><a href="#type_LibraryPathSet">LibraryPathSet</a></li><li><a href="#type_LinkedEditGroup">LinkedEditGroup</a></li><li><a href="#type_LinkedEditSuggestion">LinkedEditSuggestion</a></li><li><a href="#type_LinkedEditSuggestionKind">LinkedEditSuggestionKind</a></li><li><a href="#type_Location">Location</a></li><li><a href="#type_NavigationRegion">NavigationRegion</a></li><li><a href="#type_NavigationTarget">NavigationTarget</a></li><li><a href="#type_Occurrences">Occurrences</a></li><li><a href="#type_Outline">Outline</a></li><li><a href="#type_OverriddenMember">OverriddenMember</a></li><li><a href="#type_Override">Override</a></li><li><a href="#type_Position">Position</a></li><li><a href="#type_PostfixTemplateDescriptor">PostfixTemplateDescriptor</a></li><li><a href="#type_PubStatus">PubStatus</a></li><li><a href="#type_RefactoringFeedback">RefactoringFeedback</a></li><li><a href="#type_RefactoringKind">RefactoringKind</a></li><li><a href="#type_RefactoringMethodParameter">RefactoringMethodParameter</a></li><li><a href="#type_RefactoringMethodParameterKind">RefactoringMethodParameterKind</a></li><li><a href="#type_RefactoringOptions">RefactoringOptions</a></li><li><a href="#type_RefactoringProblem">RefactoringProblem</a></li><li><a href="#type_RefactoringProblemSeverity">RefactoringProblemSeverity</a></li><li><a href="#type_RemoveContentOverlay">RemoveContentOverlay</a></li><li><a href="#type_RequestError">RequestError</a></li><li><a href="#type_RequestErrorCode">RequestErrorCode</a></li><li><a href="#type_RuntimeCompletionExpression">RuntimeCompletionExpression</a></li><li><a href="#type_RuntimeCompletionExpressionType">RuntimeCompletionExpressionType</a></li><li><a href="#type_RuntimeCompletionExpressionTypeKind">RuntimeCompletionExpressionTypeKind</a></li><li><a href="#type_RuntimeCompletionVariable">RuntimeCompletionVariable</a></li><li><a href="#type_SearchId">SearchId</a></li><li><a href="#type_SearchResult">SearchResult</a></li><li><a href="#type_SearchResultKind">SearchResultKind</a></li><li><a href="#type_ServerService">ServerService</a></li><li><a href="#type_SourceChange">SourceChange</a></li><li><a href="#type_SourceEdit">SourceEdit</a></li><li><a href="#type_SourceFileEdit">SourceFileEdit</a></li><li><a href="#type_TypeHierarchyItem">TypeHierarchyItem</a></li></ul></div><h3>Refactorings (<a href="#refactorings">↑</a>)</h3><div class="subindex"><ul><li><a href="#refactoring_CONVERT_GETTER_TO_METHOD">CONVERT_GETTER_TO_METHOD</a></li><li><a href="#refactoring_CONVERT_METHOD_TO_GETTER">CONVERT_METHOD_TO_GETTER</a></li><li><a href="#refactoring_EXTRACT_LOCAL_VARIABLE">EXTRACT_LOCAL_VARIABLE</a></li><li><a href="#refactoring_EXTRACT_METHOD">EXTRACT_METHOD</a></li><li><a href="#refactoring_EXTRACT_WIDGET">EXTRACT_WIDGET</a></li><li><a href="#refactoring_INLINE_LOCAL_VARIABLE">INLINE_LOCAL_VARIABLE</a></li><li><a href="#refactoring_INLINE_METHOD">INLINE_METHOD</a></li><li><a href="#refactoring_MOVE_FILE">MOVE_FILE</a></li><li><a href="#refactoring_RENAME">RENAME</a></li></ul></div>
 
 
 </body></html>
\ No newline at end of file
diff --git a/pkg/analysis_server/lib/protocol/protocol_constants.dart b/pkg/analysis_server/lib/protocol/protocol_constants.dart
index fd9998b..2689506 100644
--- a/pkg/analysis_server/lib/protocol/protocol_constants.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_constants.dart
@@ -350,6 +350,8 @@
 const String SERVER_NOTIFICATION_ERROR_IS_FATAL = 'isFatal';
 const String SERVER_NOTIFICATION_ERROR_MESSAGE = 'message';
 const String SERVER_NOTIFICATION_ERROR_STACK_TRACE = 'stackTrace';
+const String SERVER_NOTIFICATION_LOG = 'server.log';
+const String SERVER_NOTIFICATION_LOG_ENTRY = 'entry';
 const String SERVER_NOTIFICATION_STATUS = 'server.status';
 const String SERVER_NOTIFICATION_STATUS_ANALYSIS = 'analysis';
 const String SERVER_NOTIFICATION_STATUS_PUB = 'pub';
diff --git a/pkg/analysis_server/lib/protocol/protocol_generated.dart b/pkg/analysis_server/lib/protocol/protocol_generated.dart
index ac2f847..8cd904d 100644
--- a/pkg/analysis_server/lib/protocol/protocol_generated.dart
+++ b/pkg/analysis_server/lib/protocol/protocol_generated.dart
@@ -15871,18 +15871,249 @@
   int get hashCode => version.hashCode;
 }
 
+/// ServerLogEntry
+///
+/// {
+///   "time": int
+///   "kind": ServerLogEntryKind
+///   "data": String
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class ServerLogEntry implements HasToJson {
+  /// The time (milliseconds since epoch) at which the server created this log
+  /// entry.
+  int time;
+
+  /// The kind of the entry, used to determine how to interpret the "data"
+  /// field.
+  ServerLogEntryKind kind;
+
+  /// The payload of the entry, the actual format is determined by the "kind"
+  /// field.
+  String data;
+
+  ServerLogEntry(this.time, this.kind, this.data);
+
+  factory ServerLogEntry.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+    json ??= {};
+    if (json is Map) {
+      int time;
+      if (json.containsKey('time')) {
+        time = jsonDecoder.decodeInt(jsonPath + '.time', json['time']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'time');
+      }
+      ServerLogEntryKind kind;
+      if (json.containsKey('kind')) {
+        kind = ServerLogEntryKind.fromJson(
+            jsonDecoder, jsonPath + '.kind', json['kind']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'kind');
+      }
+      String data;
+      if (json.containsKey('data')) {
+        data = jsonDecoder.decodeString(jsonPath + '.data', json['data']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'data');
+      }
+      return ServerLogEntry(time, kind, data);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'ServerLogEntry', json);
+    }
+  }
+
+  @override
+  Map<String, Object> toJson() {
+    var result = <String, Object>{};
+    result['time'] = time;
+    result['kind'] = kind.toJson();
+    result['data'] = data;
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is ServerLogEntry) {
+      return time == other.time && kind == other.kind && data == other.data;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => Object.hash(
+        time,
+        kind,
+        data,
+      );
+}
+
+/// ServerLogEntryKind
+///
+/// enum {
+///   NOTIFICATION
+///   RAW
+///   REQUEST
+///   RESPONSE
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class ServerLogEntryKind implements Enum {
+  /// A notification from the server, such as "analysis.highlights". The "data"
+  /// field contains a JSON object with abbreviated notification.
+  static const ServerLogEntryKind NOTIFICATION =
+      ServerLogEntryKind._('NOTIFICATION');
+
+  /// Arbitrary string, describing some event that happened in the server, e.g.
+  /// starting a file analysis, and details which files were accessed. These
+  /// entries are not structured, but provide context information about
+  /// requests and notification, and can be related by "time" for further
+  /// manual analysis.
+  static const ServerLogEntryKind RAW = ServerLogEntryKind._('RAW');
+
+  /// A request from the client, as the server views it, e.g.
+  /// "edit.getAssists". The "data" field contains a JSON object with
+  /// abbreviated request.
+  static const ServerLogEntryKind REQUEST = ServerLogEntryKind._('REQUEST');
+
+  /// Various counters and measurements related to execution of a request. The
+  /// "data" field contains a JSON object with following fields:
+  ///
+  /// - "id" - the id of the request - copied from the request.
+  /// - "method" - the method of the request, e.g. "edit.getAssists".
+  /// - "clientRequestTime" - the time (milliseconds since epoch) at which the
+  ///   client made the request - copied from the request.
+  /// - "serverRequestTime" - the time (milliseconds since epoch) at which the
+  ///   server received and decoded the JSON request.
+  /// - "responseTime" - the time (milliseconds since epoch) at which the
+  ///   server created the response to be encoded into JSON and sent to the
+  ///   client.
+  static const ServerLogEntryKind RESPONSE = ServerLogEntryKind._('RESPONSE');
+
+  /// A list containing all of the enum values that are defined.
+  static const List<ServerLogEntryKind> VALUES = <ServerLogEntryKind>[
+    NOTIFICATION,
+    RAW,
+    REQUEST,
+    RESPONSE
+  ];
+
+  @override
+  final String name;
+
+  const ServerLogEntryKind._(this.name);
+
+  factory ServerLogEntryKind(String name) {
+    switch (name) {
+      case 'NOTIFICATION':
+        return NOTIFICATION;
+      case 'RAW':
+        return RAW;
+      case 'REQUEST':
+        return REQUEST;
+      case 'RESPONSE':
+        return RESPONSE;
+    }
+    throw Exception('Illegal enum value: $name');
+  }
+
+  factory ServerLogEntryKind.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+    if (json is String) {
+      try {
+        return ServerLogEntryKind(json);
+      } catch (_) {
+        // Fall through
+      }
+    }
+    throw jsonDecoder.mismatch(jsonPath, 'ServerLogEntryKind', json);
+  }
+
+  @override
+  String toString() => 'ServerLogEntryKind.$name';
+
+  String toJson() => name;
+}
+
+/// server.log params
+///
+/// {
+///   "entry": ServerLogEntry
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class ServerLogParams implements HasToJson {
+  ServerLogEntry entry;
+
+  ServerLogParams(this.entry);
+
+  factory ServerLogParams.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+    json ??= {};
+    if (json is Map) {
+      ServerLogEntry entry;
+      if (json.containsKey('entry')) {
+        entry = ServerLogEntry.fromJson(
+            jsonDecoder, jsonPath + '.entry', json['entry']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'entry');
+      }
+      return ServerLogParams(entry);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'server.log params', json);
+    }
+  }
+
+  factory ServerLogParams.fromNotification(Notification notification) {
+    return ServerLogParams.fromJson(
+        ResponseDecoder(null), 'params', notification.params);
+  }
+
+  @override
+  Map<String, Object> toJson() {
+    var result = <String, Object>{};
+    result['entry'] = entry.toJson();
+    return result;
+  }
+
+  Notification toNotification() {
+    return Notification('server.log', toJson());
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is ServerLogParams) {
+      return entry == other.entry;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => entry.hashCode;
+}
+
 /// ServerService
 ///
 /// enum {
+///   LOG
 ///   STATUS
 /// }
 ///
 /// Clients may not extend, implement or mix-in this class.
 class ServerService implements Enum {
+  static const ServerService LOG = ServerService._('LOG');
+
   static const ServerService STATUS = ServerService._('STATUS');
 
   /// A list containing all of the enum values that are defined.
-  static const List<ServerService> VALUES = <ServerService>[STATUS];
+  static const List<ServerService> VALUES = <ServerService>[LOG, STATUS];
 
   @override
   final String name;
@@ -15891,6 +16122,8 @@
 
   factory ServerService(String name) {
     switch (name) {
+      case 'LOG':
+        return LOG;
       case 'STATUS':
         return STATUS;
     }
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 26000c7..2b98916 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -32,6 +32,7 @@
 import 'package:analysis_server/src/plugin/notification_manager.dart';
 import 'package:analysis_server/src/protocol_server.dart' as server;
 import 'package:analysis_server/src/search/search_domain.dart';
+import 'package:analysis_server/src/server/completion_request_aborting.dart';
 import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
 import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/diagnostic_server.dart';
@@ -40,6 +41,7 @@
 import 'package:analysis_server/src/server/sdk_configuration.dart';
 import 'package:analysis_server/src/services/flutter/widget_descriptions.dart';
 import 'package:analysis_server/src/utilities/process.dart';
+import 'package:analysis_server/src/utilities/request_statistics.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/exception/exception.dart';
@@ -114,6 +116,9 @@
 
   final DetachableFileSystemManager? detachableFileSystemManager;
 
+  final CompletionRequestAborting completionRequestAborting =
+      CompletionRequestAborting();
+
   /// Initialize a newly created server to receive requests from and send
   /// responses to the given [channel].
   ///
@@ -130,6 +135,7 @@
     InstrumentationService instrumentationService, {
     http.Client? httpClient,
     ProcessRunner? processRunner,
+    RequestStatisticsHelper? requestStatistics,
     DiagnosticServer? diagnosticServer,
     this.detachableFileSystemManager,
     // Disable to avoid using this in unit tests.
@@ -144,6 +150,7 @@
           httpClient,
           processRunner,
           NotificationManager(channel, baseResourceProvider.pathContext),
+          requestStatistics: requestStatistics,
           enableBazelWatcher: enableBazelWatcher,
         ) {
     var contextManagerCallbacks =
@@ -164,7 +171,7 @@
         io.pid,
       ).toNotification(),
     );
-    channel.listen(handleRequest, onDone: done, onError: error);
+    channel.requests.listen(handleRequest, onDone: done, onError: error);
     handlers = <server.RequestHandler>[
       ServerDomainHandler(this),
       AnalysisDomainHandler(this),
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index 7779957..9e758b6 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -26,6 +26,8 @@
 import 'package:analysis_server/src/utilities/file_string_sink.dart';
 import 'package:analysis_server/src/utilities/null_string_sink.dart';
 import 'package:analysis_server/src/utilities/process.dart';
+import 'package:analysis_server/src/utilities/request_statistics.dart';
+import 'package:analysis_server/src/utilities/tee_string_sink.dart';
 import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/session.dart';
@@ -125,6 +127,8 @@
   /// Performance information before initial analysis is complete.
   final ServerPerformance performanceDuringStartup = ServerPerformance();
 
+  RequestStatisticsHelper? requestStatistics;
+
   PerformanceLog? analysisPerformanceLogger;
 
   /// The set of the files that are currently priority.
@@ -151,6 +155,7 @@
     http.Client? httpClient,
     ProcessRunner? processRunner,
     this.notificationManager, {
+    this.requestStatistics,
     bool enableBazelWatcher = false,
   })  : resourceProvider = OverlayResourceProvider(baseResourceProvider),
         pubApi = PubApi(instrumentationService, httpClient,
@@ -190,6 +195,10 @@
         sink = FileStringSink(path);
       }
     }
+    final requestStatistics = this.requestStatistics;
+    if (requestStatistics != null) {
+      sink = TeeStringSink(sink, requestStatistics.perfLoggerStringSink);
+    }
     final analysisPerformanceLogger =
         this.analysisPerformanceLogger = PerformanceLog(sink);
 
diff --git a/pkg/analysis_server/lib/src/channel/byte_stream_channel.dart b/pkg/analysis_server/lib/src/channel/byte_stream_channel.dart
index 869c9b9..955e547 100644
--- a/pkg/analysis_server/lib/src/channel/byte_stream_channel.dart
+++ b/pkg/analysis_server/lib/src/channel/byte_stream_channel.dart
@@ -8,6 +8,7 @@
 
 import 'package:analysis_server/protocol/protocol.dart';
 import 'package:analysis_server/src/channel/channel.dart';
+import 'package:analysis_server/src/utilities/request_statistics.dart';
 import 'package:analyzer/instrumentation/instrumentation.dart';
 
 /// Instances of the class [ByteStreamClientChannel] implement a
@@ -72,20 +73,40 @@
 /// standard input and standard output) to communicate with clients.
 class ByteStreamServerChannel implements ServerCommunicationChannel {
   final Stream _input;
-
   final IOSink _output;
 
   /// The instrumentation service that is to be used by this analysis server.
   final InstrumentationService _instrumentationService;
 
+  /// The helper for recording request / response statistics.
+  final RequestStatisticsHelper? _requestStatistics;
+
   /// Completer that will be signalled when the input stream is closed.
   final Completer _closed = Completer();
 
   /// True if [close] has been called.
   bool _closeRequested = false;
 
+  @override
+  late final Stream<Request> requests = _input
+      .transform(const Utf8Decoder())
+      .transform(const LineSplitter())
+      .transform(
+        StreamTransformer.fromHandlers(
+          handleData: _readRequest,
+          handleDone: (sink) {
+            close();
+            sink.close();
+          },
+        ),
+      );
+
   ByteStreamServerChannel(
-      this._input, this._output, this._instrumentationService);
+      this._input, this._output, this._instrumentationService,
+      {RequestStatisticsHelper? requestStatistics})
+      : _requestStatistics = requestStatistics {
+    _requestStatistics?.serverChannel = this;
+  }
 
   /// Future that will be completed when the input stream is closed.
   Future get closed {
@@ -102,17 +123,6 @@
   }
 
   @override
-  void listen(void Function(Request request) onRequest,
-      {Function? onError, void Function()? onDone}) {
-    _input.transform(const Utf8Decoder()).transform(LineSplitter()).listen(
-        (String data) => _readRequest(data, onRequest),
-        onError: onError, onDone: () {
-      close();
-      onDone?.call();
-    });
-  }
-
-  @override
   void sendNotification(Notification notification) {
     // Don't send any further notifications after the communication channel is
     // closed.
@@ -121,7 +131,10 @@
     }
     var jsonEncoding = json.encode(notification.toJson());
     _outputLine(jsonEncoding);
-    _instrumentationService.logNotification(jsonEncoding);
+    if (!identical(notification.event, 'server.log')) {
+      _instrumentationService.logNotification(jsonEncoding);
+      _requestStatistics?.logNotification(notification);
+    }
   }
 
   @override
@@ -131,6 +144,7 @@
     if (_closeRequested) {
       return;
     }
+    _requestStatistics?.addResponse(response);
     var jsonEncoding = json.encode(response.toJson());
     _outputLine(jsonEncoding);
     _instrumentationService.logResponse(jsonEncoding);
@@ -147,7 +161,7 @@
 
   /// Read a request from the given [data] and use the given function to handle
   /// the request.
-  void _readRequest(String data, void Function(Request request) onRequest) {
+  void _readRequest(String data, Sink<Request> sink) {
     // Ignore any further requests after the communication channel is closed.
     if (_closed.isCompleted) {
       return;
@@ -160,6 +174,7 @@
       sendResponse(Response.invalidRequestFormat());
       return;
     }
-    onRequest(request);
+    _requestStatistics?.addRequest(request);
+    sink.add(request);
   }
 }
diff --git a/pkg/analysis_server/lib/src/channel/channel.dart b/pkg/analysis_server/lib/src/channel/channel.dart
index 6b5e95f..37a6e86 100644
--- a/pkg/analysis_server/lib/src/channel/channel.dart
+++ b/pkg/analysis_server/lib/src/channel/channel.dart
@@ -98,17 +98,12 @@
 /// objects that allow an [AnalysisServer] to receive [Request]s and to return
 /// both [Response]s and [Notification]s.
 abstract class ServerCommunicationChannel {
+  /// The single-subscription stream of requests.
+  Stream<Request> get requests;
+
   /// Close the communication channel.
   void close();
 
-  /// Listen to the channel for requests. If a request is received, invoke the
-  /// [onRequest] function. If an error is encountered while trying to read from
-  /// the socket, invoke the [onError] function. If the socket is closed by the
-  /// client, invoke the [onDone] function.
-  /// Only one listener is allowed per channel.
-  void listen(void Function(Request request) onRequest,
-      {Function onError, void Function() onDone});
-
   /// Send the given [notification] to the client.
   void sendNotification(Notification notification);
 
diff --git a/pkg/analysis_server/lib/src/domain_analysis.dart b/pkg/analysis_server/lib/src/domain_analysis.dart
index 1595e38..ec4c4f3 100644
--- a/pkg/analysis_server/lib/src/domain_analysis.dart
+++ b/pkg/analysis_server/lib/src/domain_analysis.dart
@@ -417,6 +417,7 @@
       }
     }
 
+    server.completionRequestAborting.abort();
     server.updateContent(request.id, params.files);
     //
     // Forward the request to the plugins.
diff --git a/pkg/analysis_server/lib/src/domain_completion.dart b/pkg/analysis_server/lib/src/domain_completion.dart
index d44110b..925a100 100644
--- a/pkg/analysis_server/lib/src/domain_completion.dart
+++ b/pkg/analysis_server/lib/src/domain_completion.dart
@@ -297,6 +297,14 @@
       return;
     }
 
+    server.completionRequestAborting.abort();
+    if (await server.completionRequestAborting.waitIfAborted(request)) {
+      return server.sendResponse(
+        CompletionGetSuggestions2Result(offset, 0, [], [], true)
+            .toResponse(request.id),
+      );
+    }
+
     var performance = OperationPerformanceImpl('<root>');
     performance.runAsync(
       'request',
@@ -501,6 +509,8 @@
           return;
         }
 
+        server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit');
+
         if (offset < 0 || offset > resolvedUnit.content.length) {
           server.sendResponse(Response.invalidParameter(
               request,
@@ -578,7 +588,7 @@
             );
             computeIncludedSetList(
               declarationsTracker,
-              resolvedUnit,
+              completionRequest,
               includedSuggestionSets,
               includedElementNames,
             );
@@ -709,11 +719,8 @@
   _RequestToPlugins? _sendRequestToPlugins(
     DartCompletionRequest completionRequest,
   ) {
-    var resolvedUnit = completionRequest.result;
-    var analysisContext = resolvedUnit.session.analysisContext;
-
     var pluginRequestParameters = plugin.CompletionGetSuggestionsParams(
-      resolvedUnit.path,
+      completionRequest.path,
       completionRequest.offset,
     );
 
@@ -722,7 +729,7 @@
       parameters: pluginRequestParameters,
       futures: server.pluginManager.broadcastRequest(
         pluginRequestParameters,
-        contextRoot: analysisContext.contextRoot,
+        contextRoot: completionRequest.analysisContext.contextRoot,
       ),
     );
   }
diff --git a/pkg/analysis_server/lib/src/domain_server.dart b/pkg/analysis_server/lib/src/domain_server.dart
index 78a3cee..559f3b8 100644
--- a/pkg/analysis_server/lib/src/domain_server.dart
+++ b/pkg/analysis_server/lib/src/domain_server.dart
@@ -49,6 +49,9 @@
     server.serverServices =
         ServerSetSubscriptionsParams.fromRequest(request).subscriptions.toSet();
 
+    server.requestStatistics?.isNotificationSubscribed =
+        server.serverServices.contains(ServerService.LOG);
+
     return ServerSetSubscriptionsResult().toResponse(request.id);
   }
 
diff --git a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
index 1d4b4b5..e8838ce 100644
--- a/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
+++ b/pkg/analysis_server/lib/src/domains/completion/available_suggestions.dart
@@ -3,29 +3,29 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/protocol_server.dart' as protocol;
+import 'package:analysis_server/src/services/completion/dart/completion_manager.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/services/available_declarations.dart';
 
-/// Compute which suggestion sets should be included into completion inside
-/// the given [resolvedUnit] of a file.  Depending on the file path, it might
+/// Compute which suggestion sets should be included into completion for
+/// the given completion [request].  Depending on the file path, it might
 /// include different sets, e.g. inside the `lib/` directory of a `Pub` package
 /// only regular dependencies can be referenced, but `test/` can reference
 /// both regular and "dev" dependencies.
 void computeIncludedSetList(
   DeclarationsTracker tracker,
-  ResolvedUnitResult resolvedUnit,
+  DartCompletionRequest request,
   List<protocol.IncludedSuggestionSet> includedSetList,
   Set<String> includedElementNames,
 ) {
-  var analysisContext = resolvedUnit.session.analysisContext;
-  var context = tracker.getContext(analysisContext);
+  var context = tracker.getContext(request.analysisContext);
   if (context == null) return;
 
-  var librariesObject = context.getLibraries(resolvedUnit.path);
+  var librariesObject = context.getLibraries(request.path);
 
-  var importedUriSet = resolvedUnit.libraryElement.importedLibraries
+  var importedUriSet = request.libraryElement.importedLibraries
       .map((importedLibrary) => importedLibrary.source.uri)
       .toSet();
 
@@ -48,7 +48,7 @@
       protocol.IncludedSuggestionSet(
         library.id,
         relevance,
-        displayUri: _getRelativeFileUri(resolvedUnit, library.uri),
+        displayUri: _getRelativeFileUri(request, library.uri),
       ),
     );
 
@@ -172,11 +172,11 @@
 }
 
 /// Computes the best URI to import [what] into the [unit] library.
-String? _getRelativeFileUri(ResolvedUnitResult unit, Uri what) {
+String? _getRelativeFileUri(DartCompletionRequest request, Uri what) {
   if (what.scheme == 'file') {
-    var pathContext = unit.session.resourceProvider.pathContext;
+    var pathContext = request.analysisSession.resourceProvider.pathContext;
 
-    var libraryPath = unit.libraryElement.source.fullName;
+    var libraryPath = request.libraryElement.source.fullName;
     var libraryFolder = pathContext.dirname(libraryPath);
 
     var whatPath = pathContext.fromUri(what);
diff --git a/pkg/analysis_server/lib/src/edit/edit_domain.dart b/pkg/analysis_server/lib/src/edit/edit_domain.dart
index 9f90b8b..b73448c 100644
--- a/pkg/analysis_server/lib/src/edit/edit_domain.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_domain.dart
@@ -207,6 +207,7 @@
     //
     var responses =
         await waitForResponses(pluginFutures, requestParameters: requestParams);
+    server.requestStatistics?.addItemTimeNow(request, 'pluginResponses');
     var converter = ResultConverter();
     var pluginChanges = <plugin.PrioritizedSourceChange>[];
     for (var response in responses) {
@@ -267,6 +268,7 @@
     //
     var responses =
         await waitForResponses(pluginFutures, requestParameters: requestParams);
+    server.requestStatistics?.addItemTimeNow(request, 'pluginResponses');
     var converter = ResultConverter();
     for (var response in responses) {
       var result = plugin.EditGetFixesResult.fromResponse(response);
@@ -591,6 +593,7 @@
       Request request, String file, int offset) async {
     var errorFixesList = <AnalysisErrorFixes>[];
     var result = await server.getResolvedUnit(file);
+    server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit');
     if (result != null) {
       var lineInfo = result.lineInfo;
       var requestLine = lineInfo.getLocation(offset).lineNumber;
@@ -641,6 +644,7 @@
         }
       }
     }
+    server.requestStatistics?.addItemTimeNow(request, 'computedFixes');
     return errorFixesList;
   }
 
@@ -736,6 +740,7 @@
     var changes = <SourceChange>[];
 
     var result = await server.getResolvedUnit(file);
+    server.requestStatistics?.addItemTimeNow(request, 'resolvedUnit');
 
     if (result != null) {
       var context = DartAssistContextImpl(
@@ -765,6 +770,8 @@
           'parameters': parametersFile,
         });
       }
+
+      server.requestStatistics?.addItemTimeNow(request, 'computedAssists');
     }
 
     return changes;
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index a0bf84c..a4eb456 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -312,7 +312,7 @@
               includedSuggestionRelevanceTags != null) {
             computeIncludedSetList(
               declarationsTracker,
-              unit,
+              completionRequest,
               includedSuggestionSets,
               includedElementNames,
             );
diff --git a/pkg/analysis_server/lib/src/server/completion_request_aborting.dart b/pkg/analysis_server/lib/src/server/completion_request_aborting.dart
new file mode 100644
index 0000000..8f03f53
--- /dev/null
+++ b/pkg/analysis_server/lib/src/server/completion_request_aborting.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:meta/meta.dart';
+
+class CompletionRequestAborting {
+  /// IDs of requests to abort on another completion request.
+  final Set<String> _pendingRequests = {};
+
+  /// IDs from [_pendingRequests] that we decided to abort.
+  final Set<String> _abortedRequests = {};
+
+  /// The function to be invoked when requests are aborted.
+  @visibleForTesting
+  void Function(Set<String> id)? onAbort;
+
+  /// Abort requests that were pending.
+  void abort() {
+    onAbort?.call(_pendingRequests);
+    _abortedRequests.addAll(_pendingRequests);
+  }
+
+  /// Return `true` if the [request] should be aborted because there is
+  /// another request in the queue, so [abort] was invoked while pumping the
+  /// event queue.
+  Future<bool> waitIfAborted(Request request) async {
+    // Mark the current request as pending.
+    var id = request.id;
+    _pendingRequests.add(id);
+
+    // Wait for more requests to arrive and abort this one.
+    await _pumpEventQueue(64);
+
+    // We are done waiting.
+    _pendingRequests.remove(id);
+
+    // See it the request was aborted.
+    return _abortedRequests.remove(id);
+  }
+
+  static Future<void> _pumpEventQueue(int times) {
+    if (times == 0) return Future.value();
+    return Future(() => _pumpEventQueue(times - 1));
+  }
+}
diff --git a/pkg/analysis_server/lib/src/server/dev_server.dart b/pkg/analysis_server/lib/src/server/dev_server.dart
index 62886da6..b11c418 100644
--- a/pkg/analysis_server/lib/src/server/dev_server.dart
+++ b/pkg/analysis_server/lib/src/server/dev_server.dart
@@ -165,21 +165,11 @@
   Stream<Notification> get onNotification => _notificationController.stream;
 
   @override
-  void close() {
-    _notificationController.close();
-  }
+  Stream<Request> get requests => _requestController.stream;
 
   @override
-  void listen(
-    void Function(Request request) onRequest, {
-    Function? onError,
-    void Function()? onDone,
-  }) {
-    _requestController.stream.listen(
-      onRequest,
-      onError: onError,
-      onDone: onDone,
-    );
+  void close() {
+    _notificationController.close();
   }
 
   @override
diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart
index a44869e..5016f76 100644
--- a/pkg/analysis_server/lib/src/server/driver.dart
+++ b/pkg/analysis_server/lib/src/server/driver.dart
@@ -24,6 +24,7 @@
 import 'package:analysis_server/src/server/sdk_configuration.dart';
 import 'package:analysis_server/src/server/stdio_server.dart';
 import 'package:analysis_server/src/socket_server.dart';
+import 'package:analysis_server/src/utilities/request_statistics.dart';
 import 'package:analysis_server/starter.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:analyzer/instrumentation/file_instrumentation.dart';
@@ -302,6 +303,7 @@
           dartSdkManager,
           crashReportingAttachmentsBuilder,
           instrumentationService,
+          RequestStatisticsHelper(),
           analytics,
           diagnosticServerPort,
           errorNotifier,
@@ -316,6 +318,7 @@
     DartSdkManager dartSdkManager,
     CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder,
     InstrumentationService instrumentationService,
+    RequestStatisticsHelper requestStatistics,
     telemetry.Analytics analytics,
     int? diagnosticServerPort,
     ErrorNotifier errorNotifier,
@@ -350,6 +353,7 @@
         dartSdkManager,
         crashReportingAttachmentsBuilder,
         instrumentationService,
+        requestStatistics,
         diagnosticServer,
         detachableFileSystemManager);
     httpServer = HttpAnalysisServer(socketServer);
diff --git a/pkg/analysis_server/lib/src/server/isolate_analysis_server.dart b/pkg/analysis_server/lib/src/server/isolate_analysis_server.dart
index 882f759..2ef30d3 100644
--- a/pkg/analysis_server/lib/src/server/isolate_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/server/isolate_analysis_server.dart
@@ -28,6 +28,7 @@
       serverIsolateChannel.stream,
       IOSink(serverIsolateChannel.sink),
       socketServer.instrumentationService,
+      requestStatistics: socketServer.requestStatistics,
     );
     socketServer.createAnalysisServer(serverChannel);
     await serverChannel.closed;
diff --git a/pkg/analysis_server/lib/src/server/stdio_server.dart b/pkg/analysis_server/lib/src/server/stdio_server.dart
index 78a036b..06c5030 100644
--- a/pkg/analysis_server/lib/src/server/stdio_server.dart
+++ b/pkg/analysis_server/lib/src/server/stdio_server.dart
@@ -27,6 +27,7 @@
       stdin,
       stdout,
       socketServer.instrumentationService,
+      requestStatistics: socketServer.requestStatistics,
     );
     socketServer.createAnalysisServer(serverChannel);
     return serverChannel.closed;
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index a5aacbf..0789ea2 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -37,6 +37,7 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
+import 'package:analyzer/src/dart/analysis/session.dart';
 import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
@@ -122,7 +123,7 @@
   }) async {
     request.checkAborted();
     var pathContext = request.resourceProvider.pathContext;
-    if (!file_paths.isDart(pathContext, request.result.path)) {
+    if (!file_paths.isDart(pathContext, request.path)) {
       return const <CompletionSuggestion>[];
     }
 
@@ -276,8 +277,14 @@
 
 /// The information about a requested list of completions within a Dart file.
 class DartCompletionRequest {
+  /// The analysis session that produced the elements of the request.
+  final AnalysisSessionImpl analysisSession;
+
   final CompletionPreference completionPreference;
 
+  /// The content of the file in which completion is requested.
+  final String content;
+
   /// Return the type imposed on the target's `containingNode` based on its
   /// context, or `null` if the context does not impose any type.
   final DartType? contextType;
@@ -291,6 +298,9 @@
   /// compute relevance scores for suggestions.
   final FeatureComputer featureComputer;
 
+  /// The library element of the file in which completion is requested.
+  final LibraryElement libraryElement;
+
   /// Return the offset within the source at which the completion is being
   /// requested.
   final int offset;
@@ -299,14 +309,13 @@
   /// request.
   final OpType opType;
 
+  /// The absolute path of the file where completion is requested.
+  final String path;
+
   /// The source range that represents the region of text that should be
   /// replaced when a suggestion is selected.
   final SourceRange replacementRange;
 
-  /// The analysis result for the file in which the completion is being
-  /// requested.
-  final ResolvedUnitResult result;
-
   /// Return the source in which the completion is being requested.
   final Source source;
 
@@ -343,34 +352,45 @@
     }
 
     return DartCompletionRequest._(
+      analysisSession: resolvedUnit.session as AnalysisSessionImpl,
       completionPreference: completionPreference,
+      content: resolvedUnit.content,
       contextType: contextType,
       dartdocDirectiveInfo: dartdocDirectiveInfo ?? DartdocDirectiveInfo(),
       documentationCache: documentationCache,
       featureComputer: featureComputer,
+      libraryElement: resolvedUnit.libraryElement,
       offset: offset,
       opType: opType,
+      path: resolvedUnit.path,
       replacementRange: target.computeReplacementRange(offset),
-      result: resolvedUnit,
       source: resolvedUnit.unit.declaredElement!.source,
       target: target,
     );
   }
 
   DartCompletionRequest._({
+    required this.analysisSession,
     required this.completionPreference,
+    required this.content,
     required this.contextType,
     required this.dartdocDirectiveInfo,
     required this.documentationCache,
     required this.featureComputer,
+    required this.libraryElement,
     required this.offset,
     required this.opType,
+    required this.path,
     required this.replacementRange,
-    required this.result,
     required this.source,
     required this.target,
   });
 
+  DriverBasedAnalysisContext get analysisContext {
+    var analysisContext = analysisSession.analysisContext;
+    return analysisContext as DriverBasedAnalysisContext;
+  }
+
   /// Return the feature set that was used to analyze the compilation unit in
   /// which suggestions are being made.
   FeatureSet get featureSet => libraryElement.featureSet;
@@ -386,10 +406,6 @@
     return entity is Expression && entity.inConstantContext;
   }
 
-  /// Return the library element which contains the unit in which the completion
-  /// is occurring.
-  LibraryElement get libraryElement => result.libraryElement;
-
   /// Answer the [DartType] for Object in dart:core
   DartType get objectType => libraryElement.typeProvider.objectType;
 
@@ -408,16 +424,11 @@
   int get replacementOffset => replacementRange.offset;
 
   /// Return the resource provider associated with this request.
-  ResourceProvider get resourceProvider => result.session.resourceProvider;
-
-  /// Return the content of the [source] in which the completion is being
-  /// requested, or `null` if the content could not be accessed.
-  String? get sourceContents => result.content;
+  ResourceProvider get resourceProvider => analysisSession.resourceProvider;
 
   /// Return the [SourceFactory] of the request.
   SourceFactory get sourceFactory {
-    var context = result.session.analysisContext as DriverBasedAnalysisContext;
-    return context.driver.sourceFactory;
+    return analysisContext.driver.sourceFactory;
   }
 
   /// Return prefix that already exists in the document for [target] or empty
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart
index 734a36e..2441362 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/local_library_contributor.dart
@@ -163,13 +163,8 @@
       return;
     }
 
-    var libraryUnits = request.result.unit.declaredElement?.library.units;
-    if (libraryUnits == null) {
-      return;
-    }
-
     var visitor = LibraryElementSuggestionBuilder(request, builder);
-    for (var unit in libraryUnits) {
+    for (var unit in request.libraryElement.units) {
       if (unit.source != request.source) {
         unit.accept(visitor);
       }
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
index 1bf599e..52456d4 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/not_imported_contributor.dart
@@ -13,7 +13,6 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/dart/analysis/file_state.dart';
-import 'package:analyzer/src/dart/analysis/session.dart';
 import 'package:analyzer/src/lint/pub.dart';
 import 'package:analyzer/src/workspace/pub.dart';
 import 'package:collection/collection.dart';
@@ -37,8 +36,8 @@
 
   @override
   Future<void> computeSuggestions() async {
-    var session = request.result.session as AnalysisSessionImpl;
-    var analysisDriver = session.getDriver(); // ignore: deprecated_member_use
+    var analysisSession = request.analysisSession;
+    var analysisDriver = request.analysisContext.driver;
 
     var fsState = analysisDriver.fsState;
     var filter = _buildFilter(fsState);
@@ -65,7 +64,7 @@
         continue;
       }
 
-      var elementResult = await session.getLibraryByUri(file.uriStr);
+      var elementResult = await analysisSession.getLibraryByUri(file.uriStr);
       if (elementResult is! LibraryElementResult) {
         continue;
       }
@@ -92,7 +91,7 @@
   }
 
   _Filter _buildFilter(FileSystemState fsState) {
-    var file = fsState.getFileForPath(request.result.path);
+    var file = fsState.getFileForPath(request.path);
     var workspacePackage = file.workspacePackage;
     if (workspacePackage is PubWorkspacePackage) {
       return _PubFilter(workspacePackage, file.path);
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
index 1766158..8ffc7d4 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_builder.dart
@@ -738,8 +738,8 @@
   Future<void> suggestOverride(SimpleIdentifier targetId,
       ExecutableElement element, bool invokeSuper) async {
     var displayTextBuffer = StringBuffer();
-    var builder = ChangeBuilder(session: request.result.session);
-    await builder.addDartFileEdit(request.result.path, (builder) {
+    var builder = ChangeBuilder(session: request.analysisSession);
+    await builder.addDartFileEdit(request.path, (builder) {
       builder.addReplacement(range.node(targetId), (builder) {
         builder.writeOverride(
           element,
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/uri_contributor.dart b/pkg/analysis_server/lib/src/services/completion/dart/uri_contributor.dart
index 703acf8..4a11a87 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/uri_contributor.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/uri_contributor.dart
@@ -57,7 +57,7 @@
             // Quoted empty string
             visitSimpleStringLiteral(uri);
           } else {
-            var data = request.sourceContents!;
+            var data = request.content;
             if (end == data.length) {
               var ch = data[end - 1];
               if (ch != '"' && ch != "'") {
@@ -69,7 +69,7 @@
           }
         }
       } else if (offset == start && offset == end) {
-        var data = request.sourceContents!;
+        var data = request.content;
         if (end == data.length) {
           var ch = data[end - 1];
           if (ch == '"' || ch == "'") {
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart b/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart
index 19d86b0..33d20fe 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/utilities.dart
@@ -197,7 +197,7 @@
 }
 
 String getRequestLineIndent(DartCompletionRequest request) {
-  var content = request.result.content;
+  var content = request.content;
   var lineStartOffset = request.offset;
   var notWhitespaceOffset = request.offset;
   for (; lineStartOffset > 0; lineStartOffset--) {
diff --git a/pkg/analysis_server/lib/src/socket_server.dart b/pkg/analysis_server/lib/src/socket_server.dart
index 5763cfe..8e938ef 100644
--- a/pkg/analysis_server/lib/src/socket_server.dart
+++ b/pkg/analysis_server/lib/src/socket_server.dart
@@ -10,6 +10,7 @@
 import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
 import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/diagnostic_server.dart';
+import 'package:analysis_server/src/utilities/request_statistics.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
 import 'package:analyzer/instrumentation/instrumentation.dart';
 import 'package:analyzer/src/generated/sdk.dart';
@@ -35,6 +36,7 @@
 
   final CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder;
   final InstrumentationService instrumentationService;
+  final RequestStatisticsHelper? requestStatistics;
   @override
   final DiagnosticServer? diagnosticServer;
   final DetachableFileSystemManager? detachableFileSystemManager;
@@ -49,6 +51,7 @@
       this.sdkManager,
       this.crashReportingAttachmentsBuilder,
       this.instrumentationService,
+      this.requestStatistics,
       this.diagnosticServer,
       this.detachableFileSystemManager);
 
@@ -59,7 +62,7 @@
       var error = RequestError(
           RequestErrorCode.SERVER_ALREADY_STARTED, 'Server already started');
       serverChannel.sendResponse(Response('', error: error));
-      serverChannel.listen((Request request) {
+      serverChannel.requests.listen((Request request) {
         serverChannel.sendResponse(Response(request.id, error: error));
       });
       return;
@@ -75,6 +78,7 @@
       sdkManager,
       crashReportingAttachmentsBuilder,
       instrumentationService,
+      requestStatistics: requestStatistics,
       diagnosticServer: diagnosticServer,
       detachableFileSystemManager: detachableFileSystemManager,
       enableBazelWatcher: true,
diff --git a/pkg/analysis_server/lib/src/utilities/mocks.dart b/pkg/analysis_server/lib/src/utilities/mocks.dart
index 9c581cd..bef5102 100644
--- a/pkg/analysis_server/lib/src/utilities/mocks.dart
+++ b/pkg/analysis_server/lib/src/utilities/mocks.dart
@@ -36,6 +36,9 @@
   MockServerChannel();
 
   @override
+  Stream<Request> get requests => requestController.stream;
+
+  @override
   void close() {
     _closed = true;
   }
@@ -46,13 +49,6 @@
   }
 
   @override
-  void listen(void Function(Request request) onRequest,
-      {Function? onError, void Function()? onDone}) {
-    requestController.stream
-        .listen(onRequest, onError: onError, onDone: onDone);
-  }
-
-  @override
   void sendNotification(Notification notification) {
     // Don't deliver notifications after the connection is closed.
     if (_closed) {
diff --git a/pkg/analysis_server/lib/src/utilities/request_statistics.dart b/pkg/analysis_server/lib/src/utilities/request_statistics.dart
new file mode 100644
index 0000000..7cdf234
--- /dev/null
+++ b/pkg/analysis_server/lib/src/utilities/request_statistics.dart
@@ -0,0 +1,256 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:analysis_server/protocol/protocol.dart';
+import 'package:analysis_server/protocol/protocol_generated.dart';
+import 'package:analysis_server/src/channel/byte_stream_channel.dart';
+
+/// Helper for tracking request handling statistics.
+///
+/// All [DateTime] are local, not UTC.
+class RequestStatisticsHelper {
+  final String _sdkVersion = Platform.version.split(' ').first;
+
+  final Map<String, _RequestStatistics> _statisticsMap = {};
+
+  /// The [StringSink] to which performance logger should copy its output.
+  late final _ServerLogStringSink _perfLoggerStringSink =
+      _ServerLogStringSink(this);
+
+  /// The channel to send 'server.log' notifications to.
+  ByteStreamServerChannel? _serverChannel;
+
+  /// Is `true` if the client subscribed for "server.log" notification.
+  bool _isNotificationSubscribed = false;
+
+  RequestStatisticsHelper();
+
+  /// Set whether the client subscribed for "server.log" notification.
+  set isNotificationSubscribed(bool value) {
+    _isNotificationSubscribed = value;
+  }
+
+  /// The [StringSink] to which performance logger should copy its output.
+  StringSink get perfLoggerStringSink => _perfLoggerStringSink;
+
+  /// The channel sets itself using this method.
+  set serverChannel(ByteStreamServerChannel serverChannel) {
+    _serverChannel = serverChannel;
+  }
+
+  /// Add a time marker item to the data associated with the [request].
+  void addItemTimeNow(Request request, String name) {
+    var id = request.id;
+    var stat = _statisticsMap[id];
+    if (stat != null) {
+      stat.items.add(
+        _RequestStatisticsItem(
+          name,
+          timeValue: DateTime.now(),
+        ),
+      );
+    }
+  }
+
+  /// The new [request] was received. Record the time when the client sent it,
+  /// and the time when the server received it (now).
+  void addRequest(Request request) {
+    _logRequest(request);
+
+    var id = request.id;
+
+    var clientRequestMilliseconds = request.clientRequestTime;
+    if (clientRequestMilliseconds == null) {
+      return;
+    }
+    var clientRequestTime = DateTime.fromMillisecondsSinceEpoch(
+      clientRequestMilliseconds,
+    );
+
+    var serverRequestTime = DateTime.now();
+
+    _statisticsMap[id] = _RequestStatistics(
+      id,
+      request.method,
+      clientRequestTime,
+      serverRequestTime,
+    );
+  }
+
+  /// The server finished processing a request, and sends the [response].
+  /// Record the time when the response is about to be sent to the client.
+  void addResponse(Response response) {
+    if (!_isNotificationSubscribed) return;
+    if (_serverChannel == null) return;
+
+    var id = response.id;
+    var stat = _statisticsMap.remove(id);
+    if (stat != null) {
+      var responseTime = DateTime.now();
+      _sendLogEntry(ServerLogEntryKind.RESPONSE, stat.toJson(responseTime));
+    }
+  }
+
+  void logNotification(Notification notification) {
+    if (!_isNotificationSubscribed) return;
+    if (_serverChannel == null) return;
+
+    var event = notification.event;
+
+    // Don't log large and often notifications.
+    if (event == 'analysis.errors' ||
+        event == 'completion.availableSuggestions') {
+      return;
+    }
+
+    var params = notification.params;
+    if (params == null) {
+      return;
+    }
+
+    var map = <String, Object>{
+      'event': event,
+    };
+
+    if (event == 'analysis.highlights' ||
+        event == 'analysis.implemented' ||
+        event == 'analysis.navigation' ||
+        event == 'analysis.outline' ||
+        event == 'analysis.overrides') {
+      map['file'] = params['file'] as String;
+    }
+
+    if (event == 'server.status') {
+      var analysis = params['analysis'];
+      if (analysis is Map<String, Object?>) {
+        map['isAnalyzing'] = analysis['isAnalyzing'] as bool;
+      }
+    }
+
+    _sendLogEntry(ServerLogEntryKind.NOTIFICATION, map);
+  }
+
+  void _logRequest(Request request) {
+    if (!_isNotificationSubscribed) return;
+    if (_serverChannel == null) return;
+
+    var method = request.method;
+    var map = <String, Object>{
+      'id': request.id,
+      'method': method,
+    };
+
+    {
+      var clientRequestTime = request.clientRequestTime;
+      if (clientRequestTime != null) {
+        map['clientRequestTime'] = clientRequestTime;
+      }
+    }
+
+    if (method == 'analysis.updateContent') {
+      var filesMap = request.params['files'];
+      if (filesMap is Map<String, Object>) {
+        map['files'] = filesMap.keys.toList();
+      }
+    } else {
+      map = request.toJson();
+    }
+
+    _sendLogEntry(ServerLogEntryKind.REQUEST, map);
+  }
+
+  void _sendLogEntry(ServerLogEntryKind kind, Object data) {
+    if (!_isNotificationSubscribed) return;
+
+    var serverChannel = _serverChannel;
+    if (serverChannel == null) return;
+
+    serverChannel.sendNotification(
+      Notification(
+        'server.log',
+        <String, Object>{
+          'time': DateTime.now().millisecondsSinceEpoch,
+          'kind': kind.toJson(),
+          'data': data,
+          'sdkVersion': _sdkVersion,
+        },
+      ),
+    );
+  }
+}
+
+class _RequestStatistics {
+  final String id;
+  final String method;
+  final DateTime clientRequestTime;
+  final DateTime serverRequestTime;
+  final List<_RequestStatisticsItem> items = [];
+
+  _RequestStatistics(
+    this.id,
+    this.method,
+    this.clientRequestTime,
+    this.serverRequestTime,
+  );
+
+  Map<String, Object> toJson(DateTime responseTime) {
+    var map = {
+      'id': id,
+      'method': method,
+      'clientRequestTime': clientRequestTime.millisecondsSinceEpoch,
+      'serverRequestTime': serverRequestTime.millisecondsSinceEpoch,
+      'responseTime': responseTime.millisecondsSinceEpoch,
+    };
+    if (items.isNotEmpty) {
+      map['items'] = items.map((item) => item.toJson()).toList();
+    }
+    return map;
+  }
+}
+
+class _RequestStatisticsItem {
+  final String name;
+  final DateTime? timeValue;
+
+  _RequestStatisticsItem(this.name, {this.timeValue});
+
+  Map<String, Object> toJson() {
+    final timeValue = this.timeValue;
+    if (timeValue != null) {
+      return {
+        'name': name,
+        'timeValue': timeValue.millisecondsSinceEpoch,
+      };
+    }
+    throw StateError('Unknown value: $name');
+  }
+}
+
+class _ServerLogStringSink implements StringSink {
+  final RequestStatisticsHelper helper;
+
+  _ServerLogStringSink(this.helper);
+
+  @override
+  void write(Object? obj) {
+    throw UnimplementedError();
+  }
+
+  @override
+  void writeAll(Iterable objects, [String separator = '']) {
+    throw UnimplementedError();
+  }
+
+  @override
+  void writeCharCode(int charCode) {
+    throw UnimplementedError();
+  }
+
+  @override
+  void writeln([Object? obj = '']) {
+    helper._sendLogEntry(ServerLogEntryKind.RAW, '$obj');
+  }
+}
diff --git a/pkg/analysis_server/lib/src/utilities/tee_string_sink.dart b/pkg/analysis_server/lib/src/utilities/tee_string_sink.dart
new file mode 100644
index 0000000..2b715c2
--- /dev/null
+++ b/pkg/analysis_server/lib/src/utilities/tee_string_sink.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A [StringSink] that writes into two other [StringSink]s.
+class TeeStringSink implements StringSink {
+  final StringSink sink1;
+  final StringSink sink2;
+
+  TeeStringSink(this.sink1, this.sink2);
+
+  @override
+  void write(Object? obj) {
+    sink1.write(obj);
+    sink2.write(obj);
+  }
+
+  @override
+  void writeAll(Iterable<dynamic> objects, [String separator = '']) {
+    sink1.writeAll(objects, separator);
+    sink2.writeAll(objects, separator);
+  }
+
+  @override
+  void writeCharCode(int charCode) {
+    sink1.writeCharCode(charCode);
+    sink2.writeCharCode(charCode);
+  }
+
+  @override
+  void writeln([Object? obj = '']) {
+    sink1.writeln(obj);
+    sink2.writeln(obj);
+  }
+}
diff --git a/pkg/analysis_server/test/channel/byte_stream_channel_test.dart b/pkg/analysis_server/test/channel/byte_stream_channel_test.dart
index 0724bfe..d89741d 100644
--- a/pkg/analysis_server/test/channel/byte_stream_channel_test.dart
+++ b/pkg/analysis_server/test/channel/byte_stream_channel_test.dart
@@ -135,7 +135,7 @@
     errorStream = errorStreamController.stream;
     var doneCompleter = Completer();
     doneFuture = doneCompleter.future;
-    channel.listen((Request request) {
+    channel.requests.listen((Request request) {
       requestStreamController.add(request);
     }, onError: (error) {
       errorStreamController.add(error);
diff --git a/pkg/analysis_server/test/domain_completion_test.dart b/pkg/analysis_server/test/domain_completion_test.dart
index 30d1d34..226a8b8 100644
--- a/pkg/analysis_server/test/domain_completion_test.dart
+++ b/pkg/analysis_server/test/domain_completion_test.dart
@@ -228,6 +228,73 @@
     NotImportedContributor.onFile = null;
   }
 
+  Future<void> test_abort_onAnotherCompletionRequest() async {
+    var abortedIdSet = <String>{};
+    server.completionRequestAborting.onAbort = (idSet) {
+      abortedIdSet.addAll(idSet);
+    };
+
+    newFile(testFilePath, content: '');
+
+    await _configureWithWorkspaceRoot();
+
+    // Send three requests, the first two should be aborted.
+    var response0 = _sendTestCompletionRequest('0', 0);
+    var response1 = _sendTestCompletionRequest('1', 0);
+    var response2 = _sendTestCompletionRequest('2', 0);
+
+    // Wait for all three.
+    var validator0 = await response0.toResult();
+    var validator1 = await response1.toResult();
+    var validator2 = await response2.toResult();
+
+    // The first two should be aborted.
+    expect(abortedIdSet, {'0', '1'});
+
+    validator0
+      ..assertIncomplete()
+      ..suggestions.assertEmpty();
+
+    validator1
+      ..assertIncomplete()
+      ..suggestions.assertEmpty();
+
+    validator2
+      ..assertComplete()
+      ..suggestions.assertCompletionsContainsAll(
+        ['int', 'double', 'Future', 'Directory'],
+      );
+  }
+
+  Future<void> test_abort_onUpdateContent() async {
+    var abortedIdSet = <String>{};
+    server.completionRequestAborting.onAbort = (idSet) {
+      abortedIdSet.addAll(idSet);
+    };
+
+    newFile(testFilePath, content: '');
+
+    await _configureWithWorkspaceRoot();
+
+    // Schedule a completion request.
+    var response = _sendTestCompletionRequest('0', 0);
+
+    // Simulate typing in the IDE.
+    await _handleSuccessfulRequest(
+      AnalysisUpdateContentParams({
+        testFilePathPlatform: AddContentOverlay('void f() {}'),
+      }).toRequest('1'),
+    );
+
+    // The request should be aborted.
+    var validator = await response.toResult();
+    expect(abortedIdSet, {'0'});
+
+    validator
+      ..assertIncomplete()
+      ..suggestions.assertEmpty();
+  }
+
   Future<void> test_notImported_abort() async {
     await _configureWithWorkspaceRoot();
 
@@ -1529,6 +1596,16 @@
       maxResults: maxResults,
     );
   }
+
+  RequestWithFutureResponse _sendTestCompletionRequest(String id, int offset) {
+    var request = CompletionGetSuggestions2Params(
+      testFilePath,
+      0,
+      1 << 10,
+    ).toRequest(id);
+    var futureResponse = _handleRequest(request);
+    return RequestWithFutureResponse(offset, request, futureResponse);
+  }
 }
 
 @reflectiveTest
@@ -2597,6 +2674,21 @@
   }
 }
 
+class RequestWithFutureResponse {
+  final int offset;
+  final Request request;
+  final Future<Response> futureResponse;
+
+  RequestWithFutureResponse(this.offset, this.request, this.futureResponse);
+
+  Future<CompletionGetSuggestions2ResponseValidator> toResult() async {
+    var response = await futureResponse;
+    expect(response, isResponseSuccess(request.id));
+    var result = CompletionGetSuggestions2Result.fromResponse(response);
+    return CompletionGetSuggestions2ResponseValidator(offset, result);
+  }
+}
+
 class SingleSuggestionValidator {
   final CompletionSuggestion suggestion;
   final List<String>? libraryUrisToImport;
diff --git a/pkg/analysis_server/test/integration/support/integration_test_methods.dart b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
index b84f147b..9a707cf 100644
--- a/pkg/analysis_server/test/integration/support/integration_test_methods.dart
+++ b/pkg/analysis_server/test/integration/support/integration_test_methods.dart
@@ -112,6 +112,16 @@
   /// Stream controller for [onServerError].
   late StreamController<ServerErrorParams> _onServerError;
 
+  /// The stream of entries describing events happened in the server.
+  ///
+  /// Parameters
+  ///
+  /// entry: ServerLogEntry
+  late Stream<ServerLogParams> onServerLog;
+
+  /// Stream controller for [onServerLog].
+  late StreamController<ServerLogParams> _onServerLog;
+
   /// Reports the current status of the server. Parameters are omitted if there
   /// has been no change in the status represented by that parameter.
   ///
@@ -2576,6 +2586,8 @@
     onServerConnected = _onServerConnected.stream.asBroadcastStream();
     _onServerError = StreamController<ServerErrorParams>(sync: true);
     onServerError = _onServerError.stream.asBroadcastStream();
+    _onServerLog = StreamController<ServerLogParams>(sync: true);
+    onServerLog = _onServerLog.stream.asBroadcastStream();
     _onServerStatus = StreamController<ServerStatusParams>(sync: true);
     onServerStatus = _onServerStatus.stream.asBroadcastStream();
     _onAnalysisAnalyzedFiles =
@@ -2648,6 +2660,10 @@
         _onServerError
             .add(ServerErrorParams.fromJson(decoder, 'params', params));
         break;
+      case 'server.log':
+        outOfTestExpect(params, isServerLogParams);
+        _onServerLog.add(ServerLogParams.fromJson(decoder, 'params', params));
+        break;
       case 'server.status':
         outOfTestExpect(params, isServerStatusParams);
         _onServerStatus
diff --git a/pkg/analysis_server/test/integration/support/protocol_matchers.dart b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
index ae6e670..5fd8c3c 100644
--- a/pkg/analysis_server/test/integration/support/protocol_matchers.dart
+++ b/pkg/analysis_server/test/integration/support/protocol_matchers.dart
@@ -1559,12 +1559,35 @@
   'WRITE'
 ]);
 
+/// ServerLogEntry
+///
+/// {
+///   "time": int
+///   "kind": ServerLogEntryKind
+///   "data": String
+/// }
+final Matcher isServerLogEntry = LazyMatcher(() => MatchesJsonObject(
+    'ServerLogEntry',
+    {'time': isInt, 'kind': isServerLogEntryKind, 'data': isString}));
+
+/// ServerLogEntryKind
+///
+/// enum {
+///   NOTIFICATION
+///   RAW
+///   REQUEST
+///   RESPONSE
+/// }
+final Matcher isServerLogEntryKind = MatchesEnum(
+    'ServerLogEntryKind', ['NOTIFICATION', 'RAW', 'REQUEST', 'RESPONSE']);
+
 /// ServerService
 ///
 /// enum {
+///   LOG
 ///   STATUS
 /// }
-final Matcher isServerService = MatchesEnum('ServerService', ['STATUS']);
+final Matcher isServerService = MatchesEnum('ServerService', ['LOG', 'STATUS']);
 
 /// SourceChange
 ///
@@ -3021,6 +3044,14 @@
 final Matcher isServerGetVersionResult = LazyMatcher(
     () => MatchesJsonObject('server.getVersion result', {'version': isString}));
 
+/// server.log params
+///
+/// {
+///   "entry": ServerLogEntry
+/// }
+final Matcher isServerLogParams = LazyMatcher(
+    () => MatchesJsonObject('server.log params', {'entry': isServerLogEntry}));
+
 /// server.setSubscriptions params
 ///
 /// {
diff --git a/pkg/analysis_server/test/socket_server_test.dart b/pkg/analysis_server/test/socket_server_test.dart
index e82b6b8..1c7bb82 100644
--- a/pkg/analysis_server/test/socket_server_test.dart
+++ b/pkg/analysis_server/test/socket_server_test.dart
@@ -101,13 +101,13 @@
   static SocketServer _createSocketServer(MockServerChannel channel) {
     final errorNotifier = ErrorNotifier();
     final server = SocketServer(
-      AnalysisServerOptions(),
-      DartSdkManager(''),
-      CrashReportingAttachmentsBuilder.empty,
-      errorNotifier,
-      null,
-      null,
-    );
+        AnalysisServerOptions(),
+        DartSdkManager(''),
+        CrashReportingAttachmentsBuilder.empty,
+        errorNotifier,
+        null,
+        null,
+        null);
 
     server.createAnalysisServer(channel);
     errorNotifier.server = server.analysisServer;
diff --git a/pkg/analysis_server/test/src/plugin/notification_manager_test.dart b/pkg/analysis_server/test/src/plugin/notification_manager_test.dart
index 0f7483d..5504afb 100644
--- a/pkg/analysis_server/test/src/plugin/notification_manager_test.dart
+++ b/pkg/analysis_server/test/src/plugin/notification_manager_test.dart
@@ -468,23 +468,10 @@
   server.Notification? sentNotification;
 
   @override
-  void close() {
-    fail('Unexpected invocation of close');
-  }
-
-  @override
-  void listen(void Function(server.Request) onRequest,
-      {Function? onError, void Function()? onDone}) {
-    fail('Unexpected invocation of listen');
-  }
+  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
 
   @override
   void sendNotification(server.Notification notification) {
     sentNotification = notification;
   }
-
-  @override
-  void sendResponse(server.Response response) {
-    fail('Unexpected invocation of sendResponse');
-  }
 }
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index 9b76591..b57a1f8 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -1226,7 +1226,7 @@
         listener: listener,
       ).computeSuggestions(dartRequest, performance);
 
-      computeIncludedSetList(declarationsTracker, dartRequest.result,
+      computeIncludedSetList(declarationsTracker, dartRequest,
           includedSuggestionSetList, includedElementNames);
 
       var includedSuggestionSetMap = {
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/ServerLogEntry.java b/pkg/analysis_server/tool/spec/generated/java/types/ServerLogEntry.java
new file mode 100644
index 0000000..c590fe5
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/generated/java/types/ServerLogEntry.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ *
+ * This file has been automatically generated. Please do not edit it manually.
+ * To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
+ */
+package org.dartlang.analysis.server.protocol;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.Lists;
+import com.google.dart.server.utilities.general.JsonUtilities;
+import com.google.dart.server.utilities.general.ObjectUtilities;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A log entry from the server.
+ *
+ * @coverage dart.server.generated.types
+ */
+@SuppressWarnings("unused")
+public class ServerLogEntry {
+
+  public static final ServerLogEntry[] EMPTY_ARRAY = new ServerLogEntry[0];
+
+  public static final List<ServerLogEntry> EMPTY_LIST = Lists.newArrayList();
+
+  /**
+   * The time (milliseconds since epoch) at which the server created this log entry.
+   */
+  private final int time;
+
+  /**
+   * The kind of the entry, used to determine how to interpret the "data" field.
+   */
+  private final String kind;
+
+  /**
+   * The payload of the entry, the actual format is determined by the "kind" field.
+   */
+  private final String data;
+
+  /**
+   * Constructor for {@link ServerLogEntry}.
+   */
+  public ServerLogEntry(int time, String kind, String data) {
+    this.time = time;
+    this.kind = kind;
+    this.data = data;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof ServerLogEntry) {
+      ServerLogEntry other = (ServerLogEntry) obj;
+      return
+        other.time == time &&
+        ObjectUtilities.equals(other.kind, kind) &&
+        ObjectUtilities.equals(other.data, data);
+    }
+    return false;
+  }
+
+  public static ServerLogEntry fromJson(JsonObject jsonObject) {
+    int time = jsonObject.get("time").getAsInt();
+    String kind = jsonObject.get("kind").getAsString();
+    String data = jsonObject.get("data").getAsString();
+    return new ServerLogEntry(time, kind, data);
+  }
+
+  public static List<ServerLogEntry> fromJsonArray(JsonArray jsonArray) {
+    if (jsonArray == null) {
+      return EMPTY_LIST;
+    }
+    ArrayList<ServerLogEntry> list = new ArrayList<ServerLogEntry>(jsonArray.size());
+    Iterator<JsonElement> iterator = jsonArray.iterator();
+    while (iterator.hasNext()) {
+      list.add(fromJson(iterator.next().getAsJsonObject()));
+    }
+    return list;
+  }
+
+  /**
+   * The payload of the entry, the actual format is determined by the "kind" field.
+   */
+  public String getData() {
+    return data;
+  }
+
+  /**
+   * The kind of the entry, used to determine how to interpret the "data" field.
+   */
+  public String getKind() {
+    return kind;
+  }
+
+  /**
+   * The time (milliseconds since epoch) at which the server created this log entry.
+   */
+  public int getTime() {
+    return time;
+  }
+
+  @Override
+  public int hashCode() {
+    HashCodeBuilder builder = new HashCodeBuilder();
+    builder.append(time);
+    builder.append(kind);
+    builder.append(data);
+    return builder.toHashCode();
+  }
+
+  public JsonObject toJson() {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("time", time);
+    jsonObject.addProperty("kind", kind);
+    jsonObject.addProperty("data", data);
+    return jsonObject;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("[");
+    builder.append("time=");
+    builder.append(time + ", ");
+    builder.append("kind=");
+    builder.append(kind + ", ");
+    builder.append("data=");
+    builder.append(data);
+    builder.append("]");
+    return builder.toString();
+  }
+
+}
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/ServerLogEntryKind.java b/pkg/analysis_server/tool/spec/generated/java/types/ServerLogEntryKind.java
new file mode 100644
index 0000000..9964b6f
--- /dev/null
+++ b/pkg/analysis_server/tool/spec/generated/java/types/ServerLogEntryKind.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+ * for details. All rights reserved. Use of this source code is governed by a
+ * BSD-style license that can be found in the LICENSE file.
+ *
+ * This file has been automatically generated. Please do not edit it manually.
+ * To regenerate the file, use the script "pkg/analysis_server/tool/spec/generate_files".
+ */
+package org.dartlang.analysis.server.protocol;
+
+/**
+ * An enumeration of the kinds of server long entries.
+ *
+ * @coverage dart.server.generated.types
+ */
+public class ServerLogEntryKind {
+
+  /**
+   * A notification from the server, such as "analysis.highlights". The "data" field contains a JSON
+   * object with abbreviated notification.
+   */
+  public static final String NOTIFICATION = "NOTIFICATION";
+
+  /**
+   * Arbitrary string, describing some event that happened in the server, e.g. starting a file
+   * analysis, and details which files were accessed. These entries are not structured, but provide
+   * context information about requests and notification, and can be related by "time" for further
+   * manual analysis.
+   */
+  public static final String RAW = "RAW";
+
+  /**
+   * A request from the client, as the server views it, e.g. "edit.getAssists". The "data" field
+   * contains a JSON object with abbreviated request.
+   */
+  public static final String REQUEST = "REQUEST";
+
+  /**
+   * Various counters and measurements related to execution of a request. The "data" field contains a
+   * JSON object with following fields:
+   *
+   * - "id" - the id of the request - copied from the request.
+   * - "method" - the method of the request, e.g. "edit.getAssists".
+   * - "clientRequestTime" - the time (milliseconds since epoch) at which the client made the request
+   *   - copied from the request.
+   * - "serverRequestTime" - the time (milliseconds since epoch) at which the server received and
+   *   decoded the JSON request.
+   * - "responseTime" - the time (milliseconds since epoch) at which the server created the response
+   *   to be encoded into JSON and sent to the client.
+   */
+  public static final String RESPONSE = "RESPONSE";
+
+}
diff --git a/pkg/analysis_server/tool/spec/generated/java/types/ServerService.java b/pkg/analysis_server/tool/spec/generated/java/types/ServerService.java
index fed7a98..99f51b9 100644
--- a/pkg/analysis_server/tool/spec/generated/java/types/ServerService.java
+++ b/pkg/analysis_server/tool/spec/generated/java/types/ServerService.java
@@ -15,6 +15,8 @@
  */
 public class ServerService {
 
+  public static final String LOG = "LOG";
+
   public static final String STATUS = "STATUS";
 
 }
diff --git a/pkg/analysis_server/tool/spec/spec_input.html b/pkg/analysis_server/tool/spec/spec_input.html
index cbbe829..aa08d74 100644
--- a/pkg/analysis_server/tool/spec/spec_input.html
+++ b/pkg/analysis_server/tool/spec/spec_input.html
@@ -354,6 +354,16 @@
       </field>
     </params>
   </notification>
+  <notification event="log" experimental="true">
+    <p>
+      The stream of entries describing events happened in the server.
+    </p>
+    <params>
+      <field name="entry">
+        <ref>ServerLogEntry</ref>
+      </field>
+    </params>
+  </notification>
   <notification event="status">
     <p>
       Reports the current status of the server. Parameters are
@@ -5314,6 +5324,7 @@
       An enumeration of the services provided by the server domain.
     </p>
     <enum>
+      <value><code>LOG</code></value>
       <value><code>STATUS</code></value>
     </enum>
   </type>
@@ -5388,6 +5399,94 @@
       </field>
     </object>
   </type>
+  <type name="ServerLogEntry" experimental="true">
+    <p>
+      A log entry from the server.
+    </p>
+    <object>
+      <field name="time">
+        <ref>int</ref>
+        <p>
+          The time (milliseconds since epoch) at which the server created
+          this log entry.
+        </p>
+      </field>
+      <field name="kind" >
+        <ref>ServerLogEntryKind</ref>
+        <p>
+          The kind of the entry, used to determine how to interpret the "data"
+          field.
+        </p>
+      </field>
+      <field name="data">
+        <ref>String</ref>
+        <p>
+          The payload of the entry, the actual format is determined by the
+          "kind" field.
+        </p>
+      </field>
+    </object>
+  </type>
+  <type name="ServerLogEntryKind" experimental="true">
+    <p>
+      An enumeration of the kinds of server long entries.
+    </p>
+    <enum>
+      <value>
+        <code>NOTIFICATION</code>
+        <p>
+          A notification from the server, such as "analysis.highlights".
+          The "data" field contains a JSON object with abbreviated notification.
+        </p>
+      </value>
+      <value>
+        <code>RAW</code>
+        <p>
+          Arbitrary string, describing some event that happened in the server,
+          e.g. starting a file analysis, and details which files were accessed.
+          These entries are not structured, but provide context information
+          about requests and notification, and can be related by "time" for
+          further manual analysis.
+        </p>
+      </value>
+      <value>
+        <code>REQUEST</code>
+        <p>
+          A request from the client, as the server views it, e.g.
+          "edit.getAssists". The "data" field contains a JSON object with
+          abbreviated request.
+        </p>
+      </value>
+      <value>
+        <code>RESPONSE</code>
+        <p>
+          Various counters and measurements related to execution of a request.
+          The "data" field contains a JSON object with following fields:
+        </p>
+        <ul>
+          <li>
+            "id" - the id of the request - copied from the request.
+          </li>
+          <li>
+            "method" - the method of the request, e.g. "edit.getAssists".
+          </li>
+          <li>
+            "clientRequestTime" - the time (milliseconds since epoch) at which
+            the client made the request - copied from the request.
+          </li>
+          <li>
+            "serverRequestTime" - the time (milliseconds since epoch) at which
+            the server received and decoded the JSON request.
+          </li>
+          <li>
+            "responseTime" - the time (milliseconds since epoch) at which the
+            server created the response to be encoded into JSON and sent to the
+            client.
+          </li>
+        </ul>
+      </value>
+    </enum>
+  </type>
 </types>
 <refactorings>
   <h2><a name="refactorings">Refactorings</a></h2>
diff --git a/pkg/analysis_server_client/lib/handler/notification_handler.dart b/pkg/analysis_server_client/lib/handler/notification_handler.dart
index f7ec39c..2d8715a 100644
--- a/pkg/analysis_server_client/lib/handler/notification_handler.dart
+++ b/pkg/analysis_server_client/lib/handler/notification_handler.dart
@@ -99,6 +99,9 @@
       case SERVER_NOTIFICATION_ERROR:
         onServerError(ServerErrorParams.fromJson(decoder, 'params', params));
         break;
+      case SERVER_NOTIFICATION_LOG:
+        onServerLog(ServerLogParams.fromJson(decoder, 'params', params));
+        break;
       case SERVER_NOTIFICATION_STATUS:
         onServerStatus(ServerStatusParams.fromJson(decoder, 'params', params));
         break;
@@ -276,6 +279,9 @@
   /// notification.
   void onServerError(ServerErrorParams params) {}
 
+  /// The stream of entries describing events happened in the server.
+  void onServerLog(ServerLogParams params) {}
+
   /// Reports the current status of the server. Parameters are
   /// omitted if there has been no change in the status
   /// represented by that parameter.
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
index fd9998b..2689506 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_constants.dart
@@ -350,6 +350,8 @@
 const String SERVER_NOTIFICATION_ERROR_IS_FATAL = 'isFatal';
 const String SERVER_NOTIFICATION_ERROR_MESSAGE = 'message';
 const String SERVER_NOTIFICATION_ERROR_STACK_TRACE = 'stackTrace';
+const String SERVER_NOTIFICATION_LOG = 'server.log';
+const String SERVER_NOTIFICATION_LOG_ENTRY = 'entry';
 const String SERVER_NOTIFICATION_STATUS = 'server.status';
 const String SERVER_NOTIFICATION_STATUS_ANALYSIS = 'analysis';
 const String SERVER_NOTIFICATION_STATUS_PUB = 'pub';
diff --git a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
index ad1ed76..2dcbd32 100644
--- a/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
+++ b/pkg/analysis_server_client/lib/src/protocol/protocol_generated.dart
@@ -15871,18 +15871,249 @@
   int get hashCode => version.hashCode;
 }
 
+/// ServerLogEntry
+///
+/// {
+///   "time": int
+///   "kind": ServerLogEntryKind
+///   "data": String
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class ServerLogEntry implements HasToJson {
+  /// The time (milliseconds since epoch) at which the server created this log
+  /// entry.
+  int time;
+
+  /// The kind of the entry, used to determine how to interpret the "data"
+  /// field.
+  ServerLogEntryKind kind;
+
+  /// The payload of the entry, the actual format is determined by the "kind"
+  /// field.
+  String data;
+
+  ServerLogEntry(this.time, this.kind, this.data);
+
+  factory ServerLogEntry.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+    json ??= {};
+    if (json is Map) {
+      int time;
+      if (json.containsKey('time')) {
+        time = jsonDecoder.decodeInt(jsonPath + '.time', json['time']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'time');
+      }
+      ServerLogEntryKind kind;
+      if (json.containsKey('kind')) {
+        kind = ServerLogEntryKind.fromJson(
+            jsonDecoder, jsonPath + '.kind', json['kind']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'kind');
+      }
+      String data;
+      if (json.containsKey('data')) {
+        data = jsonDecoder.decodeString(jsonPath + '.data', json['data']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'data');
+      }
+      return ServerLogEntry(time, kind, data);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'ServerLogEntry', json);
+    }
+  }
+
+  @override
+  Map<String, Object> toJson() {
+    var result = <String, Object>{};
+    result['time'] = time;
+    result['kind'] = kind.toJson();
+    result['data'] = data;
+    return result;
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is ServerLogEntry) {
+      return time == other.time && kind == other.kind && data == other.data;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => Object.hash(
+        time,
+        kind,
+        data,
+      );
+}
+
+/// ServerLogEntryKind
+///
+/// enum {
+///   NOTIFICATION
+///   RAW
+///   REQUEST
+///   RESPONSE
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class ServerLogEntryKind implements Enum {
+  /// A notification from the server, such as "analysis.highlights". The "data"
+  /// field contains a JSON object with abbreviated notification.
+  static const ServerLogEntryKind NOTIFICATION =
+      ServerLogEntryKind._('NOTIFICATION');
+
+  /// Arbitrary string, describing some event that happened in the server, e.g.
+  /// starting a file analysis, and details which files were accessed. These
+  /// entries are not structured, but provide context information about
+  /// requests and notification, and can be related by "time" for further
+  /// manual analysis.
+  static const ServerLogEntryKind RAW = ServerLogEntryKind._('RAW');
+
+  /// A request from the client, as the server views it, e.g.
+  /// "edit.getAssists". The "data" field contains a JSON object with
+  /// abbreviated request.
+  static const ServerLogEntryKind REQUEST = ServerLogEntryKind._('REQUEST');
+
+  /// Various counters and measurements related to execution of a request. The
+  /// "data" field contains a JSON object with following fields:
+  ///
+  /// - "id" - the id of the request - copied from the request.
+  /// - "method" - the method of the request, e.g. "edit.getAssists".
+  /// - "clientRequestTime" - the time (milliseconds since epoch) at which the
+  ///   client made the request - copied from the request.
+  /// - "serverRequestTime" - the time (milliseconds since epoch) at which the
+  ///   server received and decoded the JSON request.
+  /// - "responseTime" - the time (milliseconds since epoch) at which the
+  ///   server created the response to be encoded into JSON and sent to the
+  ///   client.
+  static const ServerLogEntryKind RESPONSE = ServerLogEntryKind._('RESPONSE');
+
+  /// A list containing all of the enum values that are defined.
+  static const List<ServerLogEntryKind> VALUES = <ServerLogEntryKind>[
+    NOTIFICATION,
+    RAW,
+    REQUEST,
+    RESPONSE
+  ];
+
+  @override
+  final String name;
+
+  const ServerLogEntryKind._(this.name);
+
+  factory ServerLogEntryKind(String name) {
+    switch (name) {
+      case 'NOTIFICATION':
+        return NOTIFICATION;
+      case 'RAW':
+        return RAW;
+      case 'REQUEST':
+        return REQUEST;
+      case 'RESPONSE':
+        return RESPONSE;
+    }
+    throw Exception('Illegal enum value: $name');
+  }
+
+  factory ServerLogEntryKind.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+    if (json is String) {
+      try {
+        return ServerLogEntryKind(json);
+      } catch (_) {
+        // Fall through
+      }
+    }
+    throw jsonDecoder.mismatch(jsonPath, 'ServerLogEntryKind', json);
+  }
+
+  @override
+  String toString() => 'ServerLogEntryKind.$name';
+
+  String toJson() => name;
+}
+
+/// server.log params
+///
+/// {
+///   "entry": ServerLogEntry
+/// }
+///
+/// Clients may not extend, implement or mix-in this class.
+class ServerLogParams implements HasToJson {
+  ServerLogEntry entry;
+
+  ServerLogParams(this.entry);
+
+  factory ServerLogParams.fromJson(
+      JsonDecoder jsonDecoder, String jsonPath, Object? json) {
+    json ??= {};
+    if (json is Map) {
+      ServerLogEntry entry;
+      if (json.containsKey('entry')) {
+        entry = ServerLogEntry.fromJson(
+            jsonDecoder, jsonPath + '.entry', json['entry']);
+      } else {
+        throw jsonDecoder.mismatch(jsonPath, 'entry');
+      }
+      return ServerLogParams(entry);
+    } else {
+      throw jsonDecoder.mismatch(jsonPath, 'server.log params', json);
+    }
+  }
+
+  factory ServerLogParams.fromNotification(Notification notification) {
+    return ServerLogParams.fromJson(
+        ResponseDecoder(null), 'params', notification.params);
+  }
+
+  @override
+  Map<String, Object> toJson() {
+    var result = <String, Object>{};
+    result['entry'] = entry.toJson();
+    return result;
+  }
+
+  Notification toNotification() {
+    return Notification('server.log', toJson());
+  }
+
+  @override
+  String toString() => json.encode(toJson());
+
+  @override
+  bool operator ==(other) {
+    if (other is ServerLogParams) {
+      return entry == other.entry;
+    }
+    return false;
+  }
+
+  @override
+  int get hashCode => entry.hashCode;
+}
+
 /// ServerService
 ///
 /// enum {
+///   LOG
 ///   STATUS
 /// }
 ///
 /// Clients may not extend, implement or mix-in this class.
 class ServerService implements Enum {
+  static const ServerService LOG = ServerService._('LOG');
+
   static const ServerService STATUS = ServerService._('STATUS');
 
   /// A list containing all of the enum values that are defined.
-  static const List<ServerService> VALUES = <ServerService>[STATUS];
+  static const List<ServerService> VALUES = <ServerService>[LOG, STATUS];
 
   @override
   final String name;
@@ -15891,6 +16122,8 @@
 
   factory ServerService(String name) {
     switch (name) {
+      case 'LOG':
+        return LOG;
       case 'STATUS':
         return STATUS;
     }
diff --git a/tests/standalone/io/directory_rename_test.dart b/tests/standalone/io/directory_rename_test.dart
new file mode 100644
index 0000000..9d31374
--- /dev/null
+++ b/tests/standalone/io/directory_rename_test.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// Directory rename test.
+
+import "dart:io";
+
+import "package:expect/expect.dart";
+import 'test_utils.dart' show withTempDir;
+
+testRenameToNewPath() async {
+  await withTempDir('testRenameToNewPath', (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+
+    dir1.renameSync("${tempDir.path}/dir2");
+    Expect.isTrue(Directory("${tempDir.path}/dir2").existsSync());
+  });
+}
+
+testRenameDoesNotAdjustPath() async {
+  await withTempDir('testRenameToNewPath', (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+    final originalPath = dir1.path;
+
+    dir1.renameSync("${tempDir.path}/dir2");
+    final finalPath = dir1.path;
+    Expect.isTrue(originalPath == finalPath,
+        "$originalPath != $finalPath - path should not be updated");
+  });
+}
+
+testRenameToSamePath() async {
+  await withTempDir('testRenameToSamePath', (Directory tempDir) async {
+    final dir = Directory("${tempDir.path}/dir");
+    dir.createSync();
+    final file = File("${dir.path}/file");
+    file.createSync();
+
+    try {
+      dir.renameSync(dir.path);
+      if (Platform.isWindows) {
+        Expect.fail('Directory.rename to same path should fail on Windows');
+      } else {
+        Expect.isTrue(file.existsSync());
+      }
+    } on FileSystemException catch (e) {
+      if (Platform.isWindows) {
+        // On Windows, the directory will be *deleted*.
+        Expect.isFalse(dir.existsSync());
+        Expect.isTrue(
+            e.osError!.message.contains('cannot find the file specified'));
+      } else {
+        Expect.fail('Directory.rename to same path should not fail on '
+            '${Platform.operatingSystem} (${Platform.operatingSystemVersion}): '
+            '$e');
+      }
+    }
+  });
+}
+
+testRenameToExistingFile() async {
+  await withTempDir('testRenameToExistingFile', (Directory tempDir) async {
+    final dir = Directory("${tempDir.path}/dir");
+    dir.createSync();
+    final file = File("${tempDir.path}/file");
+    file.createSync();
+
+    // Overwriting an exsting file is not allowed.
+    try {
+      dir.renameSync(file.path);
+      Expect.fail('Directory.rename should fail to rename a non-directory');
+    } on FileSystemException catch (e) {
+      if (Platform.isLinux || Platform.isMacOS) {
+        Expect.isTrue(e.osError!.message.contains('Not a directory'));
+      } else if (Platform.isWindows) {
+        Expect.isTrue(e.osError!.message.contains('file already exists'));
+      }
+    }
+  });
+}
+
+testRenameToExistingEmptyDirectory() async {
+  await withTempDir('testRenameToExistingEmptyDirectory',
+      (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+    File("${dir1.path}/file").createSync();
+
+    final dir2 = Directory("${tempDir.path}/dir2");
+    dir2.createSync();
+
+    dir1.renameSync(dir2.path);
+    // Verify that the file contained in dir1 have been moved.
+    Expect.isTrue(File("${dir2.path}/file").existsSync());
+  });
+}
+
+testRenameToExistingNonEmptyDirectory() async {
+  await withTempDir('testRenameToExistingNonEmptyDirectory',
+      (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+    File("${dir1.path}/file1").createSync();
+
+    final dir2 = Directory("${tempDir.path}/dir2");
+    dir2.createSync();
+    File("${dir2.path}/file2").createSync();
+
+    try {
+      dir1.renameSync(dir2.path);
+      if (Platform.isWindows) {
+        // Verify that the old directory is deleted.
+        Expect.isTrue(File("${dir2.path}/file1").existsSync());
+        Expect.isFalse(File("${dir2.path}/file2").existsSync());
+      } else {
+        Expect.fail(
+            'Directory.rename should fail to rename a non-empty directory '
+            'except on Windows');
+      }
+    } on FileSystemException catch (e) {
+      if (Platform.isLinux || Platform.isMacOS) {
+        Expect.isTrue(e.osError!.message.contains('Directory not empty'));
+      }
+    }
+  });
+}
+
+main() async {
+  await testRenameToNewPath();
+  await testRenameDoesNotAdjustPath();
+  await testRenameToSamePath();
+  await testRenameToExistingFile();
+  await testRenameToExistingEmptyDirectory();
+  await testRenameToExistingNonEmptyDirectory();
+}
diff --git a/tests/standalone/io/directory_test.dart b/tests/standalone/io/directory_test.dart
index 5c39405..c6f6e81 100644
--- a/tests/standalone/io/directory_test.dart
+++ b/tests/standalone/io/directory_test.dart
@@ -582,36 +582,6 @@
   });
 }
 
-testRename() {
-  var temp1 = Directory.systemTemp.createTempSync('directory_test');
-  var temp2 = Directory.systemTemp.createTempSync('directory_test');
-  var temp3 = temp1.renameSync(temp2.path);
-  Expect.isFalse(temp1.existsSync());
-  Expect.isTrue(temp2.existsSync());
-  Expect.equals(temp3.path, temp2.path);
-
-  var temp4 = temp2.renameSync(temp1.path);
-  Expect.isFalse(temp3.existsSync());
-  Expect.isFalse(temp2.existsSync());
-  Expect.isTrue(temp1.existsSync());
-  Expect.isTrue(temp4.existsSync());
-  Expect.equals(temp1.path, temp4.path);
-
-  String foo = '${temp4.path}/foo';
-  String bar = '${temp4.path}/bar';
-  new File(foo).createSync();
-  try {
-    new Directory(foo).renameSync(bar);
-    Expect.fail('Directory.rename should fail to rename a non-directory');
-  } on FileSystemException catch (e) {
-    if (Platform.isLinux || Platform.isMacOS) {
-      Expect.isTrue(e.osError!.message.contains('Not a directory'));
-    }
-  }
-
-  temp1.deleteSync(recursive: true);
-}
-
 main() {
   DirectoryTest.testMain();
   NestedTempDirectoryTest.testMain();
@@ -621,5 +591,4 @@
   testCreateExisting();
   testCreateDirExistingFileSync();
   testCreateDirExistingFile();
-  testRename();
 }
diff --git a/tests/standalone_2/io/directory_rename_test.dart b/tests/standalone_2/io/directory_rename_test.dart
new file mode 100644
index 0000000..e943a8a
--- /dev/null
+++ b/tests/standalone_2/io/directory_rename_test.dart
@@ -0,0 +1,140 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+//
+// Directory rename test.
+
+// @dart = 2.9
+
+import "dart:io";
+
+import "package:expect/expect.dart";
+import 'test_utils.dart' show withTempDir;
+
+testRenameToNewPath() async {
+  await withTempDir('testRenameToNewPath', (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+
+    dir1.renameSync("${tempDir.path}/dir2");
+    Expect.isTrue(Directory("${tempDir.path}/dir2").existsSync());
+  });
+}
+
+testRenameDoesNotAdjustPath() async {
+  await withTempDir('testRenameToNewPath', (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+    final originalPath = dir1.path;
+
+    dir1.renameSync("${tempDir.path}/dir2");
+    final finalPath = dir1.path;
+    Expect.isTrue(originalPath == finalPath,
+        "$originalPath != $finalPath - path should not be updated");
+  });
+}
+
+testRenameToSamePath() async {
+  await withTempDir('testRenameToSamePath', (Directory tempDir) async {
+    final dir = Directory("${tempDir.path}/dir");
+    dir.createSync();
+    final file = File("${dir.path}/file");
+    file.createSync();
+
+    try {
+      dir.renameSync(dir.path);
+      if (Platform.isWindows) {
+        Expect.fail('Directory.rename to same path should fail on Windows');
+      } else {
+        Expect.isTrue(file.existsSync());
+      }
+    } on FileSystemException catch (e) {
+      if (Platform.isWindows) {
+        // On Windows, the directory will be *deleted*.
+        Expect.isFalse(dir.existsSync());
+        Expect.isTrue(
+            e.osError.message.contains('cannot find the file specified'));
+      } else {
+        Expect.fail('Directory.rename to same path should not fail on '
+            '${Platform.operatingSystem} (${Platform.operatingSystemVersion}): '
+            '$e');
+      }
+    }
+  });
+}
+
+testRenameToExistingFile() async {
+  await withTempDir('testRenameToExistingFile', (Directory tempDir) async {
+    final dir = Directory("${tempDir.path}/dir");
+    dir.createSync();
+    final file = File("${tempDir.path}/file");
+    file.createSync();
+
+    // Overwriting an exsting file is not allowed.
+    try {
+      dir.renameSync(file.path);
+      Expect.fail('Directory.rename should fail to rename a non-directory');
+    } on FileSystemException catch (e) {
+      if (Platform.isLinux || Platform.isMacOS) {
+        Expect.isTrue(e.osError.message.contains('Not a directory'));
+      } else if (Platform.isWindows) {
+        Expect.isTrue(e.osError.message.contains('file already exists'));
+      }
+    }
+  });
+}
+
+testRenameToExistingEmptyDirectory() async {
+  await withTempDir('testRenameToExistingEmptyDirectory',
+      (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+    File("${dir1.path}/file").createSync();
+
+    final dir2 = Directory("${tempDir.path}/dir2");
+    dir2.createSync();
+
+    dir1.renameSync(dir2.path);
+    // Verify that the file contained in dir1 has been moved.
+    Expect.isTrue(File("${dir2.path}/file").existsSync());
+  });
+}
+
+testRenameToExistingNonEmptyDirectory() async {
+  await withTempDir('testRenameToExistingNonEmptyDirectory',
+      (Directory tempDir) async {
+    final dir1 = Directory("${tempDir.path}/dir1");
+    dir1.createSync();
+    File("${dir1.path}/file1").createSync();
+
+    final dir2 = Directory("${tempDir.path}/dir2");
+    dir2.createSync();
+    File("${dir2.path}/file2").createSync();
+
+    try {
+      dir1.renameSync(dir2.path);
+      if (Platform.isWindows) {
+        // Verify that the old directory is deleted.
+        Expect.isTrue(File("${dir2.path}/file1").existsSync());
+        Expect.isFalse(File("${dir2.path}/file2").existsSync());
+      } else {
+        Expect.fail(
+            'Directory.rename should fail to rename a non-empty directory '
+            'except on Windows');
+      }
+    } on FileSystemException catch (e) {
+      if (Platform.isLinux || Platform.isMacOS) {
+        Expect.isTrue(e.osError.message.contains('Directory not empty'));
+      }
+    }
+  });
+}
+
+main() async {
+  await testRenameToNewPath();
+  await testRenameDoesNotAdjustPath();
+  await testRenameToSamePath();
+  await testRenameToExistingFile();
+  await testRenameToExistingEmptyDirectory();
+  await testRenameToExistingNonEmptyDirectory();
+}
diff --git a/tests/standalone_2/io/directory_test.dart b/tests/standalone_2/io/directory_test.dart
index 91acf75..f0dad53 100644
--- a/tests/standalone_2/io/directory_test.dart
+++ b/tests/standalone_2/io/directory_test.dart
@@ -585,37 +585,6 @@
   });
 }
 
-testRename() {
-  var temp1 = Directory.systemTemp.createTempSync('directory_test');
-  var temp2 = Directory.systemTemp.createTempSync('directory_test');
-  var temp3 = temp1.renameSync(temp2.path);
-  Expect.isFalse(temp1.existsSync());
-  Expect.isTrue(temp2.existsSync());
-  Expect.equals(temp3.path, temp2.path);
-
-  var temp4 = temp2.renameSync(temp1.path);
-  Expect.isFalse(temp3.existsSync());
-  Expect.isFalse(temp2.existsSync());
-  Expect.isTrue(temp1.existsSync());
-  Expect.isTrue(temp4.existsSync());
-  Expect.equals(temp1.path, temp4.path);
-
-  String foo = '${temp4.path}/foo';
-  String bar = '${temp4.path}/bar';
-  new File(foo).createSync();
-  try {
-    new Directory(foo).renameSync(bar);
-    Expect.fail('Directory.rename should fail to rename a non-directory');
-  } catch (e) {
-    Expect.isTrue(e is FileSystemException);
-    if (Platform.isLinux || Platform.isMacOS) {
-      Expect.isTrue(e.osError.message.contains('Not a directory'));
-    }
-  }
-
-  temp1.deleteSync(recursive: true);
-}
-
 main() {
   DirectoryTest.testMain();
   NestedTempDirectoryTest.testMain();
@@ -625,5 +594,4 @@
   testCreateExisting();
   testCreateDirExistingFileSync();
   testCreateDirExistingFile();
-  testRename();
 }
diff --git a/tools/VERSION b/tools/VERSION
index cdd0b9e..41c3212 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 289
+PRERELEASE 290
 PRERELEASE_PATCH 0
\ No newline at end of file