Log Levels View

* fetch and display loglevels

* change log levels for packages

* clean up logs

* address review comments

* switch the CONNECT_TO_LS key

* Avoid caching loggers data

* add copyright headers

---------

Co-authored-by: V Udayani <vudayani@vudayaniSMD6M.vmware.com>
This commit is contained in:
vudayani-vmw
2023-12-21 02:53:32 +05:30
committed by GitHub
parent f27c4236de
commit e319e43deb
23 changed files with 894 additions and 6 deletions

View File

@@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2023 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.protocol;
public record LiveProcessLoggersSummary(String processType, String processKey, String processName, String processID,
String packageName, String effectiveLevel, String configuredLevel) {
}

View File

@@ -51,6 +51,9 @@ public interface STS4LanguageClient extends LanguageClient, SpringIndexLanguageC
@JsonNotification("sts/liveprocess/gcpauses/metrics/updated")
void liveProcessGcPausesMetricsDataUpdated(LiveProcessSummary processKey);
@JsonNotification("sts/liveprocess/loglevel/updated")
void liveProcessLogLevelUpdated(LiveProcessLoggersSummary liveProcessLoggersSummary);
@JsonNotification("sts/highlight")
void highlight(HighlightParams highlights);

View File

@@ -114,6 +114,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.Settings;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.CursorMovement;
import org.springframework.ide.vscode.commons.protocol.HighlightParams;
import org.springframework.ide.vscode.commons.protocol.LiveProcessLoggersSummary;
import org.springframework.ide.vscode.commons.protocol.LiveProcessSummary;
import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient;
import org.springframework.ide.vscode.commons.protocol.java.ClasspathListenerParams;
@@ -437,6 +438,10 @@ public class LanguageServerHarness {
receiveIndexUpdated();
}
@Override
public void liveProcessLogLevelUpdated(LiveProcessLoggersSummary processKey) {
}
});
}

View File

@@ -33,4 +33,8 @@ public interface ActuatorConnection {
Map<?, ?> getStartup() throws IOException;
String getLiveMetrics(String metricName, String tags) throws IOException;
String getLoggers() throws IOException;
String configureLogLevel(Map<String, String> args) throws IOException;
}

View File

@@ -80,6 +80,21 @@ public class HttpActuatorConnection implements ActuatorConnection {
return restTemplate.getForObject("/beans", String.class);
}
@Override
public String getLoggers() throws IOException {
return restTemplate.getForObject("/loggers", String.class);
}
@Override
public String configureLogLevel(Map<String, String> args) throws IOException {
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromPath("/loggers/"+args.get("packageName"));
if (args != null) {
uriBuilder.queryParam("configuredLevel", args.get("configuredLevel"));
}
String url = actuatorUrl + uriBuilder.toUriString();
return restTemplate.postForObject(URI.create(url), null, String.class);
}
@Override
public String getLiveMetrics(String metricName, String tags) throws IOException {
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromPath("/metrics/"+metricName);

View File

@@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright (c) 2023 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
/**
* @author Udayani V
*/
public class LoggerInfo {
private String configuredLevel;
private String effectiveLevel;
public String getConfiguredLevel() {
return configuredLevel;
}
public String getEffectiveLevel() {
return effectiveLevel;
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2023 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author Udayani V
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Loggers {
private List<String> levels;
private Map<String, LoggerInfo> loggers;
public List<String> getLevels() {
return levels;
}
public Map<String, LoggerInfo> getLoggers() {
return loggers;
}
}

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -43,6 +44,8 @@ public class SpringProcessCommandHandler {
private static final String COMMAND_LIST_CONNECTED = "sts/livedata/listConnected";
private static final String COMMAND_GET_METRICS = "sts/livedata/get/metrics";
private static final String COMMAND_GET_REFRESH_METRICS = "sts/livedata/refresh/metrics";
private static final String COMMAND_GET_LOGGERS = "sts/livedata/getLoggers";
private static final String COMMAND_CONFIGURE_LOGLEVEL = "sts/livedata/configure/logLevel";
private final SpringProcessConnectorService connectorService;
private final SpringProcessConnectorLocal localProcessConnector;
@@ -88,6 +91,16 @@ public class SpringProcessCommandHandler {
return refreshMetrics(params);
});
log.info("Registered command handler: {}",COMMAND_GET_METRICS);
server.onCommand(COMMAND_GET_LOGGERS, (params) -> {
return getLoggers(params);
});
log.info("Registered command handler: {}",COMMAND_GET_LOGGERS);
server.onCommand(COMMAND_CONFIGURE_LOGLEVEL, (params) -> {
return configureLogLevel(params);
});
log.info("Registered command handler: {}",COMMAND_CONFIGURE_LOGLEVEL);
server.onCommand(COMMAND_LIST_CONNECTED, (params) -> {
List<LiveProcessSummary> result = new ArrayList<>();
@@ -320,4 +333,34 @@ public class SpringProcessCommandHandler {
return CompletableFuture.completedFuture(null);
}
private CompletableFuture<Object> getLoggers(ExecuteCommandParams params) {
SpringProcessLoggersData loggersData = null;
SpringProcessParams springProcessParams = new SpringProcessParams();
springProcessParams.setProcessKey(getProcessKey(params));
springProcessParams.setEndpoint(getArgumentByKey(params, "endpoint"));
if (springProcessParams.getProcessKey() != null) {
loggersData = connectorService.getLoggers(springProcessParams);
return CompletableFuture.completedFuture(loggersData);
}
return CompletableFuture.completedFuture(loggersData);
}
private CompletableFuture<Object> configureLogLevel(ExecuteCommandParams params) {
Map<String, String> args = new HashMap<>();
args.put("packageName", getArgumentByKey(params, "packageName"));
args.put("configuredLevel", getArgumentByKey(params, "configuredLevel"));
args.put("effectiveLevel", getArgumentByKey(params, "effectiveLevel"));
SpringProcessParams springProcessParams = new SpringProcessParams();
springProcessParams.setProcessKey(getProcessKey(params));
springProcessParams.setArgs(args);
if (springProcessParams.getProcessKey() != null) {
connectorService.configureLogLevel(springProcessParams);
}
return CompletableFuture.completedFuture(null);
}
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.Map;
/**
* @author Martin Lippert
*/
@@ -29,4 +31,6 @@ public interface SpringProcessConnector {
String getProcessName();
SpringProcessGcPausesMetricsLiveData refreshGcPausesMetrics(SpringProcessLiveData current, String metricName, String tags) throws Exception;
SpringProcessMemoryMetricsLiveData refreshMemoryMetrics(SpringProcessLiveData current, String metricName, String tags) throws Exception;
SpringProcessLoggersData getLoggers(SpringProcessLiveData currentData) throws Exception;
SpringProcessUpdatedLogLevelData configureLogLevel(SpringProcessLiveData currentData, Map<String, String> args) throws Exception;
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.Map;
public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
private final ProcessType processType;
@@ -157,6 +159,49 @@ public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
throw new Exception("no live memory metrics data received, lets try again");
}
@Override
public SpringProcessLoggersData getLoggers(SpringProcessLiveData currentData)
throws Exception {
if (actuatorConnection != null) {
SpringProcessLoggersData loggersData = new SpringProcessLiveDataExtractorOverHttp().retrieveLoggersData(getProcessType(), actuatorConnection, processID, processName, currentData);
if (this.processID == null) {
this.processID = loggersData.getProcessID();
}
if (this.processName == null) {
this.processName = loggersData.getProcessName();
}
if (loggersData != null && loggersData.getLoggers() != null) {
return loggersData;
}
}
throw new Exception("no loggers data received, lets try again");
}
@Override
public SpringProcessUpdatedLogLevelData configureLogLevel(SpringProcessLiveData currentData, Map<String, String> args) throws Exception {
if (actuatorConnection != null) {
SpringProcessUpdatedLogLevelData springProcessUpdatedLoggersData = new SpringProcessLiveDataExtractorOverHttp().configureLogLevel(getProcessType(), actuatorConnection, processID, processName, currentData, args);
if (this.processID == null) {
this.processID = springProcessUpdatedLoggersData.getProcessID();
}
if (this.processName == null) {
this.processName = springProcessUpdatedLoggersData.getProcessName();
return springProcessUpdatedLoggersData;
}
}
throw new Exception("configure log levels failed, lets try again");
}
}

View File

@@ -210,6 +210,50 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
log.error("error closing the JMX connection for: " + jmxURL, e);
}
}
@Override
public SpringProcessLoggersData getLoggers(SpringProcessLiveData currentData) throws Exception {
log.info("try to open JMX connection to: " + jmxURL);
if (jmxConnection != null) {
try {
SpringProcessLiveDataExtractorOverJMX springJMXConnector = new SpringProcessLiveDataExtractorOverJMX();
log.info("retrieve live data from: " + jmxURL);
SpringProcessLoggersData loggersData = springJMXConnector.retrieveLoggersData(getProcessType(), jmxConnection, processID, processName, currentData);
if (loggersData != null) {
return loggersData;
}
}
catch (Exception e) {
log.error("exception while connecting to jmx: " + jmxURL, e);
}
}
throw new Exception("no loggers data received, lets try again");
}
@Override
public SpringProcessUpdatedLogLevelData configureLogLevel(SpringProcessLiveData currentData, Map<String, String> args) throws Exception {
log.info("try to open JMX connection to: " + jmxURL);
if (jmxConnection != null) {
try {
SpringProcessLiveDataExtractorOverJMX springJMXConnector = new SpringProcessLiveDataExtractorOverJMX();
log.info("retrieve live data from: " + jmxURL);
SpringProcessUpdatedLogLevelData springProcessUpdatedLoggersData = springJMXConnector.configureLogLevel(getProcessType(), jmxConnection, processID, processName, currentData, args);
return springProcessUpdatedLoggersData;
}
catch (Exception e) {
log.error("exception while connecting to jmx: " + jmxURL, e);
}
}
throw new Exception("configure log level failed, lets try again");
}
@Override
public void addConnectorChangeListener(SpringProcessConnectionChangeListener listener) {

View File

@@ -10,8 +10,10 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -34,6 +36,7 @@ public class SpringProcessConnectorService {
public static final String MEMORY = "memory";
public static final String HEAP_MEMORY = "heapMemory";
public static final String NON_HEAP_MEMORY = "nonHeapMemory";
private static final String LOGGERS = "loggers";
private static final Logger log = LoggerFactory.getLogger(SpringProcessConnectorService.class);
@@ -292,4 +295,129 @@ public class SpringProcessConnectorService {
private IndefiniteProgressTask getProgressTask(String prefixId, String title, String message) {
return this.progressService.createIndefiniteProgressTask(prefixId + progressIdKey++, title, message);
}
public SpringProcessLoggersData getLoggers(SpringProcessParams springProcessParams) {
log.info("get loggers data: " + springProcessParams.getProcessKey());
CompletableFuture<SpringProcessLoggersData> loggerData = new CompletableFuture<>();
SpringProcessConnector connector = this.connectors.get(springProcessParams.getProcessKey());
if (connector != null) {
final IndefiniteProgressTask progressTask = getProgressTask(
"spring-process-connector-service-get-loggers-data" + springProcessParams.getProcessKey(), "Loggers", null);
getLoggersData(progressTask, springProcessParams, connector, loggerData, 0, TimeUnit.SECONDS, 0);
try {
return loggerData.get();
} catch (InterruptedException | ExecutionException e) {
log.error("Failed to fetch loggers data for the process "+ springProcessParams.getProcessKey());
}
}
return null;
}
public void getLoggersData(IndefiniteProgressTask progressTask, SpringProcessParams springProcessParams, SpringProcessConnector connector, CompletableFuture<SpringProcessLoggersData> loggerData, long delay, TimeUnit unit, int retryNo) {
String processKey = springProcessParams.getProcessKey();
String endpoint = springProcessParams.getEndpoint();
String progressMessage = "Get loggers for Spring process: " + processKey + " - retry no: " + retryNo;
log.info(progressMessage);
this.scheduler.schedule(() -> {
try {
progressTask.progressEvent(progressMessage);
if(LOGGERS.equals(endpoint)) {
SpringProcessLoggersData loggersData = connector.getLoggers(this.liveDataProvider.getCurrent(processKey));
if (loggersData != null) {
loggerData.complete(loggersData);
this.connectedSuccess.put(processKey, true);
}
}
progressTask.done();
}
catch (Exception e) {
log.info("problem occured during process live data refresh", e);
if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) {
getLoggersData(progressTask, springProcessParams, connector, loggerData, retryDelayInSeconds, TimeUnit.SECONDS,
retryNo + 1);
}
else {
progressTask.done();
// Send message to client if maximum retries reached on error
if (isKnownProcessKey(processKey)) {
diagnosticService.diagnosticEvent(ShowMessageException
.error("Failed to refresh live data from process " + processKey + " after retries: " + retryNo, e));
if (!connectedSuccess.containsKey(connector.getProcessKey())) {
loggerData.complete(null);
disconnectProcess(processKey);
}
}
}
}
}, 0, TimeUnit.SECONDS);
}
public void configureLogLevel(SpringProcessParams springProcessParams) {
log.info("change log level: " + springProcessParams.getProcessKey());
SpringProcessConnector connector = this.connectors.get(springProcessParams.getProcessKey());
if (connector != null) {
final IndefiniteProgressTask progressTask = getProgressTask(
"spring-process-connector-service-configure-log-level" + springProcessParams.getProcessKey(), "Loggers", null);
configureLogLevel(progressTask, springProcessParams, connector, 0, TimeUnit.SECONDS, 0);
}
}
private void configureLogLevel(IndefiniteProgressTask progressTask, SpringProcessParams springProcessParams,
SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) {
String processKey = springProcessParams.getProcessKey();
String progressMessage = "configure log level for Spring process: " + processKey + " - retry no: " + retryNo;
log.info(progressMessage);
this.scheduler.schedule(() -> {
try {
progressTask.progressEvent(progressMessage);
SpringProcessUpdatedLogLevelData springProcessUpdatedLoggersData = connector.configureLogLevel(this.liveDataProvider.getCurrent(processKey), springProcessParams.getArgs());
if(springProcessUpdatedLoggersData != null) {
this.liveDataProvider.updateLogLevel(processKey, springProcessUpdatedLoggersData);
this.connectedSuccess.put(processKey, true);
}
progressTask.done();
}
catch (Exception e) {
log.info("problem occured during process live data refresh", e);
if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) {
configureLogLevel(progressTask, springProcessParams, connector, retryDelayInSeconds, TimeUnit.SECONDS,
retryNo + 1);
}
else {
progressTask.done();
// Send message to client if maximum retries reached on error
if (isKnownProcessKey(processKey)) {
diagnosticService.diagnosticEvent(ShowMessageException
.error("Failed to refresh live data from process " + processKey + " after retries: " + retryNo, e));
if (!connectedSuccess.containsKey(connector.getProcessKey())) {
disconnectProcess(processKey);
}
}
}
}
}, 0, TimeUnit.SECONDS);
}
}

View File

@@ -269,6 +269,114 @@ public class SpringProcessLiveDataExtractorOverHttp {
};
}
/**
* @param processType
* @param processID if null, will be determined searching existing mbeans for that information (for remote processes via platform beans runtime name)
* @param processName if null, will be determined searching existing mbeans for that information (for remote processes inferring the java command from the system properties)
* @param currentData currently stored live data
* @param metricName
* @param tags
*/
public SpringProcessLoggersData retrieveLoggersData(ProcessType processType, ActuatorConnection connection, String processID, String processName,
SpringProcessLiveData currentData) {
try {
if (processID == null) {
processID = connection.getProcessID();
}
if (processName == null) {
Properties systemProperties = connection.getSystemProperties();
if (systemProperties != null) {
String javaCommand = getJavaCommand(systemProperties);
processName = getProcessName(javaCommand);
}
}
Loggers loggers = getLoggers(connection);
return new SpringProcessLoggersData(
processType,
processName,
processID,
loggers
);
}
catch (Exception e) {
log.error("error reading live metrics data from: " + processID + " - " + processName, e);
}
return null;
}
public Loggers getLoggers(ActuatorConnection connection) {
try {
String result = connection.getLoggers();
if (result instanceof String) {
return gson.fromJson((String)result, Loggers.class);
} else if(result != null){
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(result, Loggers.class);
}
} catch (IOException e) {
// ignore
} catch (Exception e) {
log.error("Error parsing loggers", e);
}
return null;
}
public SpringProcessUpdatedLogLevelData configureLogLevel(ProcessType processType, ActuatorConnection connection, String processID, String processName,
SpringProcessLiveData currentData, Map<String, String> args) {
try {
if (processID == null) {
processID = connection.getProcessID();
}
if (processName == null) {
Properties systemProperties = connection.getSystemProperties();
if (systemProperties != null) {
String javaCommand = getJavaCommand(systemProperties);
processName = getProcessName(javaCommand);
}
}
configureLogLevel(connection, args);
return new SpringProcessUpdatedLogLevelData(
processType,
processName,
processID,
args.get("packageName"),
args.get("effectiveLevel"),
args.get("configuredLevel")
);
}
catch (Exception e) {
log.error("error reading live metrics data from: " + processID + " - " + processName + " : "+ args.get("packageName"), e);
}
return null;
}
public void configureLogLevel(ActuatorConnection connection, Map<String, String> args) {
try {
connection.configureLogLevel(args);
} catch (IOException e) {
// ignore
} catch (Exception e) {
log.error("Error parsing response", e);
throw e;
}
return;
}
private StartupMetricsModel getStartupMetrics(ActuatorConnection connection, StartupMetricsModel currentStartup) {
if (currentStartup != null) {
return currentStartup;
@@ -415,7 +523,7 @@ public class SpringProcessLiveDataExtractorOverHttp {
} catch (IOException e) {
// ignore
} catch (Exception e) {
log.error("Error parsing beans", e);
log.error("Error parsing live metrics", e);
}
return null;
}

View File

@@ -299,6 +299,124 @@ public class SpringProcessLiveDataExtractorOverJMX {
};
}
/**
* @param processType
* @param processID if null, will be determined searching existing mbeans for that information (for remote processes via platform beans runtime name)
* @param processName if null, will be determined searching existing mbeans for that information (for remote processes inferring the java command from the system properties)
* @param currentData currently stored live data
*/
public SpringProcessLoggersData retrieveLoggersData(ProcessType processType, JMXConnector jmxConnector, String processID, String processName,
SpringProcessLiveData currentData) {
try {
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
String domain = getDomainForActuator(connection);
if (processID == null) {
processID = getProcessID(connection);
}
if (processName == null) {
Properties systemProperties = getSystemProperties(connection);
if (systemProperties != null) {
String javaCommand = getJavaCommand(systemProperties);
processName = getProcessName(javaCommand);
}
}
Loggers loggers = getLoggers(connection, domain);
return new SpringProcessLoggersData(
processType,
processName,
processID,
loggers
);
}
catch (Exception e) {
log.error("error reading live metrics data from: " + processID + " - " + processName, e);
}
return null;
}
public Loggers getLoggers(MBeanServerConnection connection, String domain) {
Object[] params1 = new Object[] {};
String[] signature = new String[] {String.class.getName(), List.class.getName()};
try {
Object loggersData = getActuatorDataFromOperation(connection,
getObjectName(domain, "type=Endpoint,name=Loggers"),
"loggers",
params1,
signature);
if (loggersData instanceof String) {
return gson.fromJson((String)loggersData, Loggers.class);
} else if(loggersData != null){
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.convertValue(loggersData, Loggers.class);
}
} catch (Exception e) {
log.error("", e);
}
return null;
}
public SpringProcessUpdatedLogLevelData configureLogLevel(ProcessType processType, JMXConnector jmxConnector,
String processID, String processName, SpringProcessLiveData currentData, Map<String, String> args) {
try {
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
String domain = getDomainForActuator(connection);
if (processID == null) {
processID = getProcessID(connection);
}
if (processName == null) {
Properties systemProperties = getSystemProperties(connection);
if (systemProperties != null) {
String javaCommand = getJavaCommand(systemProperties);
processName = getProcessName(javaCommand);
}
}
changeLogLevel(connection, domain, args);
return new SpringProcessUpdatedLogLevelData(
processType,
processName,
processID,
args.get("packageName"),
args.get("effectiveLevel"),
args.get("configuredLevel")
);
}
catch (Exception e) {
log.error("error changing log level : " + processID + " - " + processName + " : "+args.get("packageName"), e);
}
return null;
}
public void changeLogLevel(MBeanServerConnection connection, String domain, Map<String, String> args) throws Exception {
Object[] params = new Object[] {args.get("packageName"), args.get("configuredLevel")};
String[] signature = new String[] {String.class.getName(), List.class.getName()};
try {
getActuatorDataFromOperation(connection,
getObjectName(domain, "type=Endpoint,name=Loggers"),
"configureLogLevel",
params,
signature);
} catch (Exception e) {
log.error("", e);
throw e;
}
return;
}
private StartupMetricsModel getStartupMetrics(MBeanServerConnection connection, String domain, StartupMetricsModel currentStartup) {
if (currentStartup != null) {
return currentStartup;
@@ -751,6 +869,4 @@ public class SpringProcessLiveDataExtractorOverJMX {
return null;
}
}

View File

@@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.LiveProcessLoggersSummary;
import org.springframework.ide.vscode.commons.protocol.LiveProcessSummary;
import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient;
import org.springframework.ide.vscode.commons.util.Assert;
@@ -29,6 +30,7 @@ public class SpringProcessLiveDataProvider {
private final ConcurrentMap<String, SpringProcessLiveData> liveData;
private final ConcurrentMap<String, SpringProcessMemoryMetricsLiveData> memoryMetricsLiveData;
private final ConcurrentMap<String, SpringProcessGcPausesMetricsLiveData> gcPausesMetricsLiveData;
private final ConcurrentMap<String, SpringProcessLoggersData> loggersData;
private final List<SpringProcessLiveDataChangeListener> listeners;
private final SimpleLanguageServer server;
@@ -37,6 +39,7 @@ public class SpringProcessLiveDataProvider {
this.liveData = new ConcurrentHashMap<>();
this.memoryMetricsLiveData = new ConcurrentHashMap<>();
this.gcPausesMetricsLiveData = new ConcurrentHashMap<>();
this.loggersData = new ConcurrentHashMap<>();
this.listeners = new CopyOnWriteArrayList<>();
}
@@ -104,6 +107,11 @@ public class SpringProcessLiveDataProvider {
getClient().liveProcessGcPausesMetricsDataUpdated(createGcPausesMetricsSummary(processKey, liveData));
}
public void updateLogLevel(String processKey, SpringProcessUpdatedLogLevelData updatedLogLevelData) {
getClient().liveProcessLogLevelUpdated(createUpdatedLogLevelSummary(processKey, updatedLogLevelData));
}
public void addLiveDataChangeListener(SpringProcessLiveDataChangeListener listener) {
this.listeners.add(listener);
}
@@ -131,6 +139,10 @@ public class SpringProcessLiveDataProvider {
public SpringProcessGcPausesMetricsLiveData getGcPausesMetrics(String processKey) {
return this.gcPausesMetricsLiveData.get(processKey);
}
public SpringProcessLoggersData getLoggersData(String processKey) {
return this.loggersData.get(processKey);
}
public static LiveProcessSummary createProcessSummary(String processKey, SpringProcessLiveData liveData) {
LiveProcessSummary p = new LiveProcessSummary();
@@ -159,4 +171,11 @@ public class SpringProcessLiveDataProvider {
return p;
}
public static LiveProcessLoggersSummary createUpdatedLogLevelSummary(String processKey, SpringProcessUpdatedLogLevelData updatedLogLevelData) {
LiveProcessLoggersSummary p = new LiveProcessLoggersSummary(updatedLogLevelData.getProcessType().jsonName(),
processKey, updatedLogLevelData.getProcessName(), updatedLogLevelData.getProcessID(),
updatedLogLevelData.getPackageName(), updatedLogLevelData.getEffectiveLevel(),
updatedLogLevelData.getConfiguredLevel());
return p;
}
}

View File

@@ -0,0 +1,49 @@
/*******************************************************************************
* Copyright (c) 2023 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
/**
* @author Udayani V
*/
public class SpringProcessLoggersData {
private final ProcessType processType;
private final String processName;
private final String processID;
private final Loggers loggers;
public SpringProcessLoggersData(ProcessType processType, String processName, String processID,
Loggers loggers) {
super();
this.processType = processType;
this.processName = processName;
this.processID = processID;
this.loggers = loggers;
}
public ProcessType getProcessType() {
return processType;
}
public String getProcessName() {
return processName;
}
public String getProcessID() {
return processID;
}
public Loggers getLoggers() {
return loggers;
}
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.Map;
/**
* @author V Udayani
*/
@@ -19,7 +21,7 @@ public class SpringProcessParams {
private String endpoint;
private String metricName;
private String tags;
private Map<String, String> args;
public SpringProcessParams() {
}
@@ -62,4 +64,12 @@ public class SpringProcessParams {
this.tags = tags;
}
public Map<String, String> getArgs() {
return args;
}
public void setArgs(Map<String, String> args) {
this.args = args;
}
}

View File

@@ -0,0 +1,59 @@
/*******************************************************************************
* Copyright (c) 2023 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
public class SpringProcessUpdatedLogLevelData {
private final ProcessType processType;
private final String processName;
private final String processID;
private final String packageName;
private final String effectiveLevel;
private final String configuredLevel;
public SpringProcessUpdatedLogLevelData(ProcessType processType, String processName, String processID,
String packageName, String effectiveLevel, String configuredLevel) {
super();
this.processType = processType;
this.processName = processName;
this.processID = processID;
this.packageName = packageName;
this.effectiveLevel = effectiveLevel;
this.configuredLevel = configuredLevel;
}
public ProcessType getProcessType() {
return processType;
}
public String getProcessName() {
return processName;
}
public String getProcessID() {
return processID;
}
public String getPackageName() {
return packageName;
}
public String getEffectiveLevel() {
return effectiveLevel;
}
public String getConfiguredLevel() {
return configuredLevel;
}
}

View File

@@ -13,6 +13,7 @@ import { ApiManager } from "./apiManager";
import { ExtensionAPI } from "./api";
import {registerClasspathService} from "@pivotal-tools/commons-vscode/lib/classpath";
import {registerJavaDataService} from "@pivotal-tools/commons-vscode/lib/java-data";
import * as setLogLevelUi from './set-log-levels-ui';
const PROPERTIES_LANGUAGE_ID = "spring-boot-properties";
const YAML_LANGUAGE_ID = "spring-boot-properties-yaml";
@@ -146,6 +147,7 @@ export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAP
VSCode.commands.registerCommand('vscode-spring-boot.ls.stop', () => client.stop());
liveHoverUi.activate(client, options, context);
rewrite.activate(client, options, context);
setLogLevelUi.activate(client, options, context);
VSCode.commands.registerCommand('vscode-spring-boot.spring.modulith.metadata.refresh', async () => {
const modulithProjects = await VSCode.commands.executeCommand('sts/modulith/projects');

View File

@@ -8,9 +8,8 @@ import {
LiveProcessUpdatedNotification,
LiveProcessGcPausesMetricsUpdatedNotification,
LiveProcessMemoryMetricsUpdatedNotification,
SpringIndexUpdatedNotification
SpringIndexUpdatedNotification,
} from "./notification";
import VSCode from "vscode";
import {RequestType} from "vscode-languageclient";
export class ApiManager {
@@ -29,6 +28,7 @@ export class ApiManager {
const onDidLiveProcessGcPausesMetricsUpdate = this.onDidLiveProcessGcPausesMetricsUpdateEmitter.event;
const onDidLiveProcessMemoryMetricsUpdate = this.onDidLiveProcessMemoryMetricsUpdateEmitter.event;
const onSpringIndexUpdated = this.onSpringIndexUpdateEmitter.event;
// const onDidLiveProcessLoggersUpdate = this.onDidLiveProcessLoggersUpdateEmitter.event;
const COMMAND_LIVEDATA_GET = "sts/livedata/get";
const getLiveProcessData = async (query) => {

View File

@@ -10,6 +10,18 @@ export interface LiveProcess {
processName: string;
}
/**
* Information returned by notification for updated log level for the live process
*/
export interface LiveProcessUpdatedLogLevel {
type: string;
processKey: string;
processName: string;
packageName: string;
effectiveLevel: string;
configuredLevel: string;
}
/**
* Specialized interface for type 'local' LiveProcess.
*/
@@ -40,4 +52,8 @@ export namespace LiveProcessMemoryMetricsUpdatedNotification {
export namespace SpringIndexUpdatedNotification {
export const type = new NotificationType<void>('spring/index/updated');
}
export namespace LiveProcessLogLevelUpdatedNotification {
export const type = new NotificationType<LiveProcessUpdatedLogLevel>('sts/liveprocess/loglevel/updated');
}

View File

@@ -0,0 +1,133 @@
'use strict';
import * as VSCode from 'vscode';
import { LanguageClient } from "vscode-languageclient/node";
import { ActivatorOptions } from '@pivotal-tools/commons-vscode';
import { LiveProcess, LiveProcessLogLevelUpdatedNotification, LiveProcessUpdatedLogLevel } from './notification';
interface ProcessCommandInfo {
processKey : string;
label: string;
action: string;
projectName: string;
}
export interface Logger {
configuredLevel: string;
effectiveLevel: string;
}
export interface Loggers {
[propName: string]: Logger;
}
export interface LoggersData {
levels: string[];
loggers: Loggers;
}
export interface ProcessLoggersData {
loggers: LoggersData;
processID: string;
processName: string;
processType: number;
}
export interface LoggerItem {
logger: Logger;
name: string;
}
async function setLogLevelHandler() {
const processData : ProcessCommandInfo[] = await VSCode.commands.executeCommand('sts/livedata/listProcesses');
const choiceMap = new Map<string, ProcessCommandInfo>();
const choices : string[] = [];
processData.forEach(p => {
const slash = p.action.lastIndexOf('/');
if (slash>=0) {
const choiceLabel = p.label;
choiceMap.set(choiceLabel, p);
choices.push(choiceLabel);
}
});
if (choices) {
const picked = await VSCode.window.showQuickPick(choices);
if (picked) {
const chosen = choiceMap.get(picked);
try {
const loggers: ProcessLoggersData = await getLoggers(chosen);
await displayLoggers(loggers, chosen.processKey);
} catch (error) {
VSCode.window.showErrorMessage("Failed to fetch loggers for the process " + chosen.processKey);
}
}
}
}
async function getLoggers(processInfo: ProcessCommandInfo): Promise<ProcessLoggersData> {
return new Promise(async (resolve, reject) => {
await VSCode.window.withProgress({
location: VSCode.ProgressLocation.Window,
title: "Fetching Loggers Data for process "+processInfo.processKey,
cancellable: false
}, async (progress) => {
try {
const loggers: ProcessLoggersData = await VSCode.commands.executeCommand('sts/livedata/getLoggers', processInfo, {"endpoint": "loggers"});
progress.report({});
resolve(loggers);
} catch (error) {
reject(error);
}
});
});
}
async function displayLoggers(processLoggersData: ProcessLoggersData, processKey: string) {
let items;
const loggersData = processLoggersData.loggers;
if(loggersData.loggers) {
items = Object.keys(loggersData.loggers).map(packageName => {
const logger: Logger = loggersData.loggers[packageName];
const effectiveLevel = logger.effectiveLevel;
const label = packageName + ' (' + effectiveLevel + ')';
return {
packageName,
effectiveLevel,
label
};
});
}
if(items) {
const chosenPackage = await VSCode.window.showQuickPick(items);
if (chosenPackage) {
const chosenlogLevel = await VSCode.window.showQuickPick(loggersData.levels);
await VSCode.commands.executeCommand('sts/livedata/configure/logLevel', {"processKey": processKey}, chosenPackage, {"configuredLevel":chosenlogLevel});
}
}
}
async function logLevelUpdated(process: LiveProcessUpdatedLogLevel) {
VSCode.window.showInformationMessage("The Log level for " + process.packageName + " has been updated from " +
process.effectiveLevel + " to " + process.configuredLevel);
}
/** Called when extension is activated */
export function activate(
client: LanguageClient,
options: ActivatorOptions,
context: VSCode.ExtensionContext
) {
client.onNotification(LiveProcessLogLevelUpdatedNotification.type, logLevelUpdated)
context.subscriptions.push(
VSCode.commands.registerCommand('vscode-spring-boot.set.log-levels', () => {
if (client.isRunning()) {
return setLogLevelHandler();
} else {
VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects");
}
})
);
}

View File

@@ -129,6 +129,11 @@
"command": "vscode-spring-boot.spring.modulith.metadata.refresh",
"title": "Refresh Modulith Metadata",
"category": "Spring Boot"
},
{
"command": "vscode-spring-boot.set.log-levels",
"title": "Set Log Levels",
"category": "Spring Boot"
}
],
"configuration": [