diff --git a/pkg/analysis_server/integration_test/lsp_server/integration_tests.dart b/pkg/analysis_server/integration_test/lsp_server/integration_tests.dart index e6de1a90f81..4a2f0fd08dd 100644 --- a/pkg/analysis_server/integration_test/lsp_server/integration_tests.dart +++ b/pkg/analysis_server/integration_test/lsp_server/integration_tests.dart @@ -9,7 +9,6 @@ import 'dart:io'; import 'package:analysis_server/lsp_protocol/protocol.dart'; import 'package:analysis_server/src/lsp/channel/lsp_byte_stream_channel.dart'; import 'package:analysis_server/src/services/pub/pub_command.dart'; -import 'package:analysis_server/src/session_logger/session_logger.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/instrumentation/instrumentation.dart'; import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart'; @@ -242,7 +241,6 @@ class LspServerClient { inputStream, outputStream, instrumentationService ?? InstrumentationLogAdapter(PrintableLogger()), - sessionLogger: SessionLogger(), )..listen(_serverToClient.add); } diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart index d0c4f7bb43a..1a03b46cb9a 100644 --- a/pkg/analysis_server/lib/src/analysis_server.dart +++ b/pkg/analysis_server/lib/src/analysis_server.dart @@ -1169,6 +1169,7 @@ abstract class AnalysisServer { await contextManager.dispose(); await analyticsManager.shutdown(); await shutdownPerfWitness(); + await sessionLogger.shutdown(); } ResolvedForCompletionResultImpl? diff --git a/pkg/analysis_server/lib/src/lsp/channel/lsp_byte_stream_channel.dart b/pkg/analysis_server/lib/src/lsp/channel/lsp_byte_stream_channel.dart index 918bc334978..c5250158025 100644 --- a/pkg/analysis_server/lib/src/lsp/channel/lsp_byte_stream_channel.dart +++ b/pkg/analysis_server/lib/src/lsp/channel/lsp_byte_stream_channel.dart @@ -25,7 +25,7 @@ class LspByteStreamServerChannel implements LspServerCommunicationChannel { final InstrumentationService _instrumentationService; /// The session logger. - final SessionLogger _sessionLogger; + final SessionLogger? _sessionLogger; /// Completer that will be signalled when the input stream is closed. final Completer _closed = Completer(); @@ -37,8 +37,8 @@ class LspByteStreamServerChannel implements LspServerCommunicationChannel { this._input, this._output, this._instrumentationService, { - SessionLogger? sessionLogger, - }) : _sessionLogger = sessionLogger ?? SessionLogger(); + this._sessionLogger, + }); /// Future that will be completed when the input stream is closed. @override @@ -94,7 +94,7 @@ class LspByteStreamServerChannel implements LspServerCommunicationChannel { } _instrumentationService.logRequest(data); var json = jsonDecode(data) as Map; - _sessionLogger.logMessage( + _sessionLogger?.logMessage( from: ProcessId.ide, to: ProcessId.server, message: json, @@ -129,7 +129,7 @@ class LspByteStreamServerChannel implements LspServerCommunicationChannel { _write(utf8EncodedBody); _instrumentationService.logResponse(jsonEncodedBody); - _sessionLogger.logMessage( + _sessionLogger?.logMessage( from: ProcessId.server, to: ProcessId.ide, message: json, diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart index fd754e37f05..2eab66f636f 100644 --- a/pkg/analysis_server/lib/src/server/driver.dart +++ b/pkg/analysis_server/lib/src/server/driver.dart @@ -343,10 +343,7 @@ class Driver implements ServerStarter { // Initialize the session logging service. var sessionLogFilePath = results.option(sessionLogOption); - var sessionLogFile = sessionLogFilePath == null - ? null - : PhysicalResourceProvider.INSTANCE.getFile(sessionLogFilePath); - _sessionLogger = SessionLogger(sessionLogFile: sessionLogFile); + _sessionLogger = SessionLogger(filePath: sessionLogFilePath); _sessionLogger.normalizer.addReplacementsForPath( defaultSdkPath, 'dartSdkRoot', diff --git a/pkg/analysis_server/lib/src/session_logger/session_logger.dart b/pkg/analysis_server/lib/src/session_logger/session_logger.dart index 9634e8784a0..2eb21e4fb2e 100644 --- a/pkg/analysis_server/lib/src/session_logger/session_logger.dart +++ b/pkg/analysis_server/lib/src/session_logger/session_logger.dart @@ -8,7 +8,6 @@ import 'package:analysis_server/src/session_logger/log_entry.dart'; import 'package:analysis_server/src/session_logger/log_normalizer.dart'; import 'package:analysis_server/src/session_logger/process_id.dart'; import 'package:analysis_server/src/session_logger/session_logger_sink.dart'; -import 'package:analyzer/file_system/file_system.dart'; /// Used to write information about a session to a log. class SessionLogger { @@ -24,12 +23,12 @@ class SessionLogger { /// /// If [filePath] is non-`null`, it also writes log entries to a file at /// [filePath]. - factory SessionLogger({File? sessionLogFile}) { + factory SessionLogger({String? filePath}) { var normalizer = LogNormalizer(); var sink = SessionLoggerInMemorySink( maxBufferLength: 1024, normalizer: normalizer, - sessionLogFile: sessionLogFile, + sessionLogFilePath: filePath, ); return SessionLogger._(sink: sink, normalizer: normalizer); } @@ -82,5 +81,7 @@ class SessionLogger { } /// Shuts down the logger. - Future shutdown() async {} + Future shutdown() async { + await sink?.close(); + } } diff --git a/pkg/analysis_server/lib/src/session_logger/session_logger_sink.dart b/pkg/analysis_server/lib/src/session_logger/session_logger_sink.dart index 476c5147f1d..bccfbe37a0d 100644 --- a/pkg/analysis_server/lib/src/session_logger/session_logger_sink.dart +++ b/pkg/analysis_server/lib/src/session_logger/session_logger_sink.dart @@ -3,29 +3,34 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:convert'; +import 'dart:io' as io; import 'package:analysis_server/src/session_logger/log_entry.dart'; import 'package:analysis_server/src/session_logger/log_normalizer.dart'; import 'package:analysis_server/src/session_logger/process_id.dart'; -import 'package:analyzer/file_system/file_system.dart'; import 'package:language_server_protocol/protocol_special.dart' show Either2; /// A sink for a session logger that will write entries to a file. final class SessionLoggerFileSink extends SessionLoggerSink { /// The sink used to write to the file. - final File _file; + late final io.IOSink _sink; @override final LogNormalizer _normalizer; /// Initializes a newly created sink to write to the file at the given /// [filePath]. - SessionLoggerFileSink(this._file, {required this._normalizer}); + SessionLoggerFileSink(String filePath, {required this._normalizer}) { + _sink = io.File(filePath).openWrite(); + } + + @override + Future close() => _sink.close(); @override void writeLogEntry(JsonMap entry) { var jsonString = _normalizer.normalize(json.encode(entry)); - _file.writeAsStringSync('$jsonString\n'); + _sink.writeln(jsonString); } } @@ -71,11 +76,11 @@ final class SessionLoggerInMemorySink extends SessionLoggerSink { SessionLoggerInMemorySink({ required this.maxBufferLength, required LogNormalizer normalizer, - File? sessionLogFile, + String? sessionLogFilePath, }) : _normalizer = normalizer, - _nextLogger = sessionLogFile == null + _nextLogger = sessionLogFilePath == null ? null - : SessionLoggerFileSink(sessionLogFile, normalizer: normalizer); + : SessionLoggerFileSink(sessionLogFilePath, normalizer: normalizer); /// Returns a list of the entries that have been captured. /// @@ -209,6 +214,8 @@ sealed class SessionLoggerSink { /// The normalizer used to normalize paths in log entries. LogNormalizer get _normalizer; + Future close() async {} + /// Writes the given log [entry] to this sink. void writeLogEntry(JsonMap entry); diff --git a/pkg/analysis_server/test/src/session_logger/session_logger_sink_test.dart b/pkg/analysis_server/test/src/session_logger/session_logger_sink_test.dart index fcf0ca7e77b..a09c7777018 100644 --- a/pkg/analysis_server/test/src/session_logger/session_logger_sink_test.dart +++ b/pkg/analysis_server/test/src/session_logger/session_logger_sink_test.dart @@ -2,11 +2,14 @@ // 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' as io; + import 'package:analysis_server/src/session_logger/log_normalizer.dart'; import 'package:analysis_server/src/session_logger/session_logger_sink.dart'; import 'package:analyzer/file_system/memory_file_system.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer_testing/utilities/extensions/resource_provider.dart'; -import 'package:path/path.dart' as path show Context; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; @@ -17,25 +20,55 @@ void main() { }); } +/// Test the [SessionLoggerFileSink], an implementation of [SessionLoggerSink] +/// that writes to the physical disk via `dart:io`. It doesn't use the +/// analyzer file abstraction, as it uses `openWrite()` and `IOSink` which are +/// not implemented in the abstraction. @reflectiveTest class SessionLoggerFileSinkTest { late LogNormalizer normalizer; - late MemoryResourceProvider provider; - late path.Context pathContext; + late io.Directory tempDirectory; + late String logPath; + late io.File logFile; + PhysicalResourceProvider provider = PhysicalResourceProvider.INSTANCE; + late path.Context pathContext = provider.pathContext; + + late String Function(String) convertPath = ResourceProviderExtension( + provider, + ).convertPath; void setUp() { - provider = MemoryResourceProvider(); - pathContext = provider.pathContext; normalizer = LogNormalizer(); + tempDirectory = io.Directory.systemTemp.createTempSync( + 'dartServer_sessionLog_fileSinkTest', + ); + logPath = path.join(tempDirectory.path, 'foo.txt'); + logFile = io.File(logPath); + } + + void tearDown() { + tempDirectory.deleteSync(recursive: true); + } + + Future test_multipleWrites() async { + var fileSink = SessionLoggerFileSink(logPath, normalizer: normalizer); + + // Write multiple entries + fileSink.writeLogEntry({'id': 1}); + fileSink.writeLogEntry({'id': 2}); + fileSink.writeLogEntry({'id': 3}); + + await fileSink.close(); + + // Ensure they are all recorded. + var content = io.File(logPath).readAsStringSync(); + expect(content, '{"id":1}\n{"id":2}\n{"id":3}\n'); } Future test_normalized() async { - var convertPath = ResourceProviderExtension(provider).convertPath; - var logPath = convertPath('/foo.txt'); var pathToNormalize = convertPath('/path/to/normalize'); - var logFile = provider.getFile(logPath); - var fileSink = SessionLoggerFileSink(logFile, normalizer: normalizer); + var fileSink = SessionLoggerFileSink(logPath, normalizer: normalizer); normalizer.addReplacementsForPath(pathToNormalize, 'normalized'); fileSink.writeLogEntry({ 'kind': 'message', @@ -49,7 +82,9 @@ class SessionLoggerFileSinkTest { }, }); - var content = logFile.readAsStringSync(); + await fileSink.close(); + + var content = io.File(logPath).readAsStringSync(); expect( content, '{"kind":"message",'