blob: 4587f89ed5035c20ae5648f866b97d1afebef463 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Red Hat Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Lucas Bullen (Red Hat Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.lsp4e;
import java.io.File;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.lsp4e.server.StreamConnectionProvider;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
public class LoggingStreamConnectionProviderProxy implements StreamConnectionProvider {
public static final String LOG_DIRECTORY = "languageServers-log"; //$NON-NLS-1$
private static final String FILE_KEY = "file.logging.disabled"; //$NON-NLS-1$
private static final String STDERR_KEY = "stderr.logging.enabled"; //$NON-NLS-1$
private StreamConnectionProvider provider;
private InputStream inputStream;
private OutputStream outputStream;
private InputStream errorStream;
private String id;
private File currentFile;
private File folder;
private boolean logToFile;
private boolean logToConsole;
/**
* Converts a language server ID to the preference ID for logging communications
* to file from the language server
*
* @return language server's preference ID for file logging
*/
public static String lsToFileLoggingId(String serverId) {
return serverId + "." + FILE_KEY;//$NON-NLS-1$
}
/**
* Converts a language server ID to the preference ID for logging communications
* to console from the language server
*
* @return language server's preference ID for console logging
*/
public static String lsToConsoleLoggingId(String serverId) {
return serverId + "." + STDERR_KEY;//$NON-NLS-1$
}
/**
* Returns whether currently created connections should be logged to file or the
* standard error stream.
*
* @return If connections should be logged
*/
public static boolean shouldLog(String serverId) {
IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore();
return !store.getBoolean(lsToFileLoggingId(serverId)) || store.getBoolean(lsToConsoleLoggingId(serverId));
}
public LoggingStreamConnectionProviderProxy(StreamConnectionProvider provider, String serverId) {
this.id = serverId;
this.provider = provider;
IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore();
logToFile = !store.getBoolean(lsToFileLoggingId(serverId));
logToConsole = store.getBoolean(lsToConsoleLoggingId(serverId));
store.addPropertyChangeListener(new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
if (event.getProperty().equals(FILE_KEY)) {
logToFile = (boolean) event.getNewValue();
} else if (event.getProperty().equals(STDERR_KEY)) {
logToConsole = (boolean) event.getNewValue();
}
}
});
}
@Override
public InputStream getInputStream() {
if (inputStream != null) {
return inputStream;
}
if (provider.getInputStream() != null) {
inputStream = new FilterInputStream(provider.getInputStream()) {
@Override
public int read(byte[] b, int off, int len) throws IOException {
int bytes = super.read(b, off, len);
byte[] payload = new byte[bytes];
System.arraycopy(b, off, payload, 0, bytes);
if (logToConsole) {
logToConsole(id + " to LSP4E:" + new String(payload)); //$NON-NLS-1$
}
if (logToFile) {
logToFile("\n" + id + " to LSP4E:" + new String(payload)); //$NON-NLS-1$ //$NON-NLS-2$
}
return bytes;
}
};
}
return inputStream;
}
@Override
public InputStream getErrorStream() {
if (errorStream != null) {
return errorStream;
}
if (provider.getErrorStream() != null) {
errorStream = new FilterInputStream(provider.getErrorStream()) {
@Override
public int read(byte[] b, int off, int len) throws IOException {
int bytes = super.read(b, off, len);
byte[] payload = new byte[bytes];
System.arraycopy(b, off, payload, 0, bytes);
if (logToConsole) {
logToConsole("Error from " + id + ":" + new String(payload)); //$NON-NLS-1$ //$NON-NLS-2$
}
if (logToFile) {
logToFile(new String("\nError from:" + id + ":" + payload)); //$NON-NLS-1$ //$NON-NLS-2$
}
return bytes;
}
};
}
return errorStream;
}
@Override
public OutputStream getOutputStream() {
if (outputStream != null) {
return outputStream;
}
if (provider.getOutputStream() != null) {
outputStream = new FilterOutputStream(provider.getOutputStream()) {
@Override
public void write(byte[] b) throws IOException {
if (logToConsole) {
logToConsole("LSP4E to " + id + ":" + new String(b)); //$NON-NLS-1$ //$NON-NLS-2$
}
if (logToFile) {
logToFile("\nLSP4E to " + id + ":" + new String(b)); //$NON-NLS-1$ //$NON-NLS-2$
}
super.write(b);
}
};
}
return outputStream;
}
@Override
public void start() throws IOException {
provider.start();
}
@Override
public InputStream forwardCopyTo(InputStream input, OutputStream output) {
return provider.forwardCopyTo(input, output);
}
@Override
public Object getInitializationOptions(URI rootUri) {
return provider.getInitializationOptions(rootUri);
}
@Override
public String getTrace(URI rootUri) {
return provider.getTrace(rootUri);
}
@Override
public void handleMessage(Message message, LanguageServer languageServer, URI rootURI) {
provider.handleMessage(message, languageServer, rootURI);
}
@Override
public void stop() {
provider.stop();
try {
if (outputStream != null) {
outputStream.close();
outputStream = null;
}
if (inputStream != null) {
inputStream.close();
inputStream = null;
}
if (errorStream != null) {
errorStream.close();
errorStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void logToConsole(String string) {
if (consoleStream == null || consoleStream.isClosed()) {
consoleStream = findConsole().newMessageStream();
}
consoleStream.println(string);
}
private MessageConsoleStream consoleStream;
private MessageConsole findConsole() {
ConsolePlugin plugin = ConsolePlugin.getDefault();
IConsoleManager conMan = plugin.getConsoleManager();
IConsole[] existing = conMan.getConsoles();
for (int i = 0; i < existing.length; i++)
if (LanguageServerPlugin.PLUGIN_ID.equals(existing[i].getName()))
return (MessageConsole) existing[i];
// no console found, so create a new one
MessageConsole myConsole = new MessageConsole(LanguageServerPlugin.PLUGIN_ID, null);
conMan.addConsoles(new IConsole[] { myConsole });
return myConsole;
}
private void logToFile(String string) {
if (currentFile == null || !currentFile.exists() || !currentFile.isFile() || !currentFile.canWrite()) {
generateNewLogFile();
if (currentFile == null) {
return;
}
}
try {
Files.write(currentFile.toPath(), string.getBytes(), StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
return;
}
private void generateNewLogFile() {
File folder = getFolder();
if (folder == null) {
return;
}
currentFile = new File(folder, id + ".log"); //$NON-NLS-1$
try {
currentFile.createNewFile();
} catch (IOException e) {
currentFile = null;
e.printStackTrace();
}
}
private File getFolder() {
if (folder != null && folder.exists() && folder.isDirectory() && folder.canWrite()) {
return folder;
}
IPath root = ResourcesPlugin.getWorkspace().getRoot().getLocation();
if (root == null) {
return null;
}
File folder = new File(root.addTrailingSeparator().toPortableString() + LOG_DIRECTORY);
if (folder != null && (folder.exists() || folder.mkdirs()) && folder.isDirectory() && folder.canWrite()) {
return folder;
}
return null;
}
}