Skip to content

Commit c5a35eb

Browse files
andrewL-avlqmickaelistria
authored andcommitted
feat: send LS error stream to Eclipse log.
Forward lines written by the language server to std_err into the Eclipse log.
1 parent bbf499f commit c5a35eb

4 files changed

Lines changed: 43 additions & 9 deletions

File tree

org.eclipse.lsp4e.tests.mock/META-INF/MANIFEST.MF

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: Mock Language Server to test LSP4E
44
Bundle-SymbolicName: org.eclipse.lsp4e.tests.mock
5-
Bundle-Version: 0.16.17.qualifier
5+
Bundle-Version: 0.16.18.qualifier
66
Bundle-Vendor: Eclipse LSP4E
77
Bundle-RequiredExecutionEnvironment: JavaSE-17
88
Require-Bundle: org.eclipse.lsp4j,

org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockConnectionProvider.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111
*******************************************************************************/
1212
package org.eclipse.lsp4e.tests.mock;
1313

14-
import java.io.ByteArrayInputStream;
1514
import java.io.Closeable;
1615
import java.io.IOException;
1716
import java.io.InputStream;
1817
import java.io.OutputStream;
1918
import java.net.URI;
2019
import java.nio.channels.Channels;
2120
import java.nio.channels.Pipe;
22-
import java.nio.charset.StandardCharsets;
2321
import java.util.ArrayList;
2422
import java.util.Collection;
2523
import java.util.concurrent.ExecutorService;
@@ -49,7 +47,7 @@ public class MockConnectionProvider implements StreamConnectionProvider {
4947
public void start() throws IOException {
5048
Pipe serverOutputToClientInput = Pipe.open();
5149
Pipe clientOutputToServerInput = Pipe.open();
52-
errorStream = new ByteArrayInputStream("Error output on console".getBytes(StandardCharsets.UTF_8));
50+
errorStream = InputStream.nullInputStream();
5351

5452
InputStream serverInputStream = Channels.newInputStream(clientOutputToServerInput.source());
5553
OutputStream serverOutputStream = Channels.newOutputStream(serverOutputToClientInput.sink());

org.eclipse.lsp4e.tests.mock/src/org/eclipse/lsp4e/tests/mock/MockConnectionProviderMultiRootFolders.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@
1212
*******************************************************************************/
1313
package org.eclipse.lsp4e.tests.mock;
1414

15-
import java.io.ByteArrayInputStream;
1615
import java.io.Closeable;
1716
import java.io.IOException;
1817
import java.io.InputStream;
1918
import java.io.OutputStream;
2019
import java.nio.channels.Channels;
2120
import java.nio.channels.Pipe;
22-
import java.nio.charset.StandardCharsets;
2321
import java.util.ArrayList;
2422
import java.util.Collection;
2523
import java.util.concurrent.ExecutorService;
@@ -72,7 +70,7 @@ public void start() throws IOException {
7270
try {
7371
Pipe serverOutputToClientInput = Pipe.open();
7472
Pipe clientOutputToServerInput = Pipe.open();
75-
errorStream = new ByteArrayInputStream("Error output on console".getBytes(StandardCharsets.UTF_8));
73+
errorStream = InputStream.nullInputStream();
7674

7775
InputStream serverInputStream = Channels.newInputStream(clientOutputToServerInput.source());
7876
OutputStream serverOutputStream = Channels.newOutputStream(serverOutputToClientInput.sink());

org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919

2020
import static org.eclipse.lsp4e.internal.NullSafetyHelper.castNonNull;
2121

22+
import java.io.BufferedReader;
2223
import java.io.File;
2324
import java.io.IOException;
25+
import java.io.InputStreamReader;
2426
import java.io.UncheckedIOException;
2527
import java.net.URI;
2628
import java.util.ArrayList;
@@ -263,6 +265,7 @@ synchronized void close() {
263265
private final ExecutorService dispatcher;
264266
private final ExecutorService listener;
265267
private final ExecutorService cleaner;
268+
private final ExecutorService errorProcessor;
266269

267270
private LanguageServerContext context = new LanguageServerContext();
268271

@@ -306,6 +309,11 @@ private LanguageServerWrapper(@Nullable IProject project, LanguageServerDefiniti
306309
final var livenessThreadNameFormat = formatPrefix + "#cleaner"; //$NON-NLS-1$
307310
this.cleaner = Executors
308311
.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(livenessThreadNameFormat).build());
312+
313+
// Executor service to run a thread processing the LS error stream.
314+
final var errorsThreadNameFormat = formatPrefix + "#errorProcessor"; //$NON-NLS-1$
315+
this.errorProcessor = Executors
316+
.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(errorsThreadNameFormat).build());
309317
}
310318

311319
void stopDispatcher() {
@@ -319,7 +327,10 @@ void stopDispatcher() {
319327
this.listener.shutdownNow();
320328

321329
// Again only needed for testing as the cleaner should exit when the launcher terminates.
322-
this.cleaner.shutdownNow();
330+
this.cleaner.shutdown();
331+
332+
// Similarly, the error stream should also close when the input/output streams close.
333+
this.errorProcessor.shutdownNow();
323334
}
324335

325336
/**
@@ -463,7 +474,7 @@ private synchronized void start(boolean forceRestart) {
463474
if (!initFuture.isDone()) {
464475
workingContext.languageServer = null;
465476
initFuture.completeExceptionally(new ExecutionException(
466-
"Unexpected language server termination", launchException)); //$NON-NLS-1$
477+
"Unexpected language server termination: " + getFullErrorStream(), launchException)); //$NON-NLS-1$
467478
}
468479
}, cleaner);
469480
return initFuture;
@@ -491,6 +502,9 @@ private synchronized void start(boolean forceRestart) {
491502
}
492503
});
493504
FileBuffers.getTextFileBufferManager().addFileBufferListener(fileBufferListener);
505+
castNonNull(initializeFuture).thenRunAsync(() -> {
506+
processErrorStream(castNonNull(context.lspStreamProvider), l -> LanguageServerPlugin.getDefault().getLog().error(l), e -> {throw new UncheckedIOException(e);});
507+
}, errorProcessor);
494508
}
495509
}).exceptionally(e -> {
496510
shutdown(workingContext);
@@ -577,6 +591,30 @@ private CompletableFuture<InitializeResult> initServer(final @Nullable URI rootU
577591
// FIXME race: this.context may not be what it is expected to be, should be parameter
578592
}
579593

594+
private String getFullErrorStream() {
595+
StringBuilder builder = new StringBuilder();
596+
processErrorStream(castNonNull(context.lspStreamProvider), l -> builder.append(l + '\n'), e -> builder.append("Exception processing error stream: " + e)); //$NON-NLS-1$
597+
if (!builder.isEmpty()) {
598+
return builder.toString();
599+
}
600+
return "No errors recorded."; //$NON-NLS-1$
601+
}
602+
603+
private void processErrorStream(StreamConnectionProvider streamProvider, Consumer<String> lineHandler, Consumer<IOException> exceptionHandler) {
604+
var errorStream = streamProvider.getErrorStream();
605+
if (errorStream != null) {
606+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) {
607+
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
608+
if (!line.isBlank()) {
609+
lineHandler.accept(line);
610+
}
611+
}
612+
} catch (IOException e) {
613+
exceptionHandler.accept(e);
614+
}
615+
}
616+
}
617+
580618
@Nullable
581619
public ProcessHandle getProcessHandle() {
582620
return Adapters.adapt(context.lspStreamProvider, ProcessHandle.class);

0 commit comments

Comments
 (0)