mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
Add log websocket (#3859)
This adds a websocket connection for receiving logs. Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
+1
@@ -30,6 +30,7 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.openhab.core.auth.AuthenticationException;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.io.rest.auth.AuthFilter;
|
||||
import org.openhab.core.io.websocket.event.EventWebSocketAdapter;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket;
|
||||
package org.openhab.core.io.websocket.event;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket;
|
||||
package org.openhab.core.io.websocket.event;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket;
|
||||
package org.openhab.core.io.websocket.event;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
+2
-1
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket;
|
||||
package org.openhab.core.io.websocket.event;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.io.websocket.WebSocketAdapter;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket;
|
||||
package org.openhab.core.io.websocket.event;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket.log;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.io.websocket.event.EventDTO;
|
||||
import org.osgi.service.log.LogLevel;
|
||||
|
||||
/**
|
||||
* The {@link EventDTO} is used for serialization and deserialization of events
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LogDTO {
|
||||
public @Nullable String loggerName;
|
||||
public @Nullable LogLevel level;
|
||||
public @Nullable Date timestamp;
|
||||
public long unixtime;
|
||||
public @Nullable String message;
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket.log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.StatusCode;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.osgi.service.log.LogEntry;
|
||||
import org.osgi.service.log.LogListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* The {@link LogWebSocket} is the WebSocket implementation for logs
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@WebSocket
|
||||
@NonNullByDefault
|
||||
@SuppressWarnings("unused")
|
||||
public class LogWebSocket implements LogListener {
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final TypeToken<List<String>> STRING_LIST_TYPE = (TypeToken<List<String>>) TypeToken
|
||||
.getParameterized(List.class, String.class);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LogWebSocket.class);
|
||||
|
||||
private final LogWebSocketAdapter wsAdapter;
|
||||
private final Gson gson;
|
||||
|
||||
private @Nullable Session session;
|
||||
private @Nullable RemoteEndpoint remoteEndpoint;
|
||||
private String remoteIdentifier = "<unknown>";
|
||||
|
||||
private List<Pattern> loggerPatterns = List.of();
|
||||
|
||||
public LogWebSocket(Gson gson, LogWebSocketAdapter wsAdapter) {
|
||||
this.wsAdapter = wsAdapter;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
public void onClose(int statusCode, String reason) {
|
||||
this.wsAdapter.unregisterListener(this);
|
||||
remoteIdentifier = "<unknown>";
|
||||
this.session = null;
|
||||
this.remoteEndpoint = null;
|
||||
}
|
||||
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
this.session = session;
|
||||
RemoteEndpoint remoteEndpoint = session.getRemote();
|
||||
this.remoteEndpoint = remoteEndpoint;
|
||||
this.remoteIdentifier = remoteEndpoint.getInetSocketAddress().toString();
|
||||
this.wsAdapter.registerListener(this);
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onText(String message) {
|
||||
RemoteEndpoint remoteEndpoint = this.remoteEndpoint;
|
||||
if (session == null || remoteEndpoint == null) {
|
||||
// no connection or no remote endpoint , do nothing this is possible due to async behavior
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loggerPatterns = gson.fromJson(message, STRING_LIST_TYPE).stream().map(Pattern::compile).toList();
|
||||
} catch (JsonParseException e) {
|
||||
logger.warn("Failed to parse '{}' to a list of subscribed loggers", message);
|
||||
}
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onError(Session session, @Nullable Throwable error) {
|
||||
if (session != null) {
|
||||
session.close();
|
||||
}
|
||||
|
||||
String message = error == null ? "<null>" : Objects.requireNonNullElse(error.getMessage(), "<null>");
|
||||
logger.info("WebSocket error: {}", message);
|
||||
onClose(StatusCode.NO_CODE, message);
|
||||
}
|
||||
|
||||
private synchronized void sendMessage(String message) throws IOException {
|
||||
RemoteEndpoint remoteEndpoint = this.remoteEndpoint;
|
||||
if (remoteEndpoint == null) {
|
||||
logger.warn("Could not determine remote endpoint, failed to send '{}'.", message);
|
||||
return;
|
||||
}
|
||||
remoteEndpoint.sendString(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logged(@NonNullByDefault({}) LogEntry logEntry) {
|
||||
if (!loggerPatterns.isEmpty() && loggerPatterns.stream().noneMatch(logMatch(logEntry))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
LogDTO logDTO = map(logEntry);
|
||||
sendMessage(gson.toJson(logDTO));
|
||||
} catch (IOException e) {
|
||||
logger.debug("Failed to send log {} to {}: {}", logEntry, remoteIdentifier, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Predicate<Pattern> logMatch(LogEntry logEntry) {
|
||||
return pattern -> pattern.matcher(logEntry.getLoggerName()).matches();
|
||||
}
|
||||
|
||||
private static LogDTO map(LogEntry logEntry) {
|
||||
LogDTO logDTO = new LogDTO();
|
||||
logDTO.loggerName = logEntry.getLoggerName();
|
||||
logDTO.level = logEntry.getLogLevel();
|
||||
logDTO.unixtime = logEntry.getTime();
|
||||
logDTO.timestamp = new Date(logEntry.getTime());
|
||||
logDTO.message = logEntry.getMessage();
|
||||
return logDTO;
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.io.websocket.log;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
|
||||
import org.openhab.core.io.websocket.WebSocketAdapter;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.log.LogReaderService;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link LogWebSocketAdapter} allows subscription to log events over WebSocket
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true, service = { WebSocketAdapter.class })
|
||||
public class LogWebSocketAdapter implements WebSocketAdapter {
|
||||
public static final String ADAPTER_ID = "logs";
|
||||
private final Gson gson = new Gson();
|
||||
private final Set<LogWebSocket> webSockets = new CopyOnWriteArraySet<>();
|
||||
private final LogReaderService logReaderService;
|
||||
|
||||
@Activate
|
||||
public LogWebSocketAdapter(@Reference LogReaderService logReaderService) {
|
||||
this.logReaderService = logReaderService;
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
webSockets.forEach(logReaderService::removeLogListener);
|
||||
}
|
||||
|
||||
public void registerListener(LogWebSocket eventWebSocket) {
|
||||
webSockets.add(eventWebSocket);
|
||||
logReaderService.addLogListener(eventWebSocket);
|
||||
}
|
||||
|
||||
public void unregisterListener(LogWebSocket eventWebSocket) {
|
||||
logReaderService.removeLogListener(eventWebSocket);
|
||||
webSockets.remove(eventWebSocket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return ADAPTER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest,
|
||||
ServletUpgradeResponse servletUpgradeResponse) {
|
||||
return new LogWebSocket(gson, LogWebSocketAdapter.this);
|
||||
}
|
||||
}
|
||||
+1
@@ -43,6 +43,7 @@ import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.auth.AuthenticationException;
|
||||
import org.openhab.core.io.rest.auth.AnonymousUserSecurityContext;
|
||||
import org.openhab.core.io.rest.auth.AuthFilter;
|
||||
import org.openhab.core.io.websocket.event.EventWebSocket;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
|
||||
/**
|
||||
|
||||
+6
-2
@@ -18,8 +18,8 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.core.io.websocket.EventWebSocket.WEBSOCKET_EVENT_TYPE;
|
||||
import static org.openhab.core.io.websocket.EventWebSocket.WEBSOCKET_TOPIC_PREFIX;
|
||||
import static org.openhab.core.io.websocket.event.EventWebSocket.WEBSOCKET_EVENT_TYPE;
|
||||
import static org.openhab.core.io.websocket.event.EventWebSocket.WEBSOCKET_TOPIC_PREFIX;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -39,6 +39,10 @@ import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.io.websocket.event.EventDTO;
|
||||
import org.openhab.core.io.websocket.event.EventWebSocket;
|
||||
import org.openhab.core.io.websocket.event.EventWebSocketAdapter;
|
||||
import org.openhab.core.io.websocket.event.ItemEventUtility;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
|
||||
+3
@@ -27,6 +27,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.io.websocket.event.EventDTO;
|
||||
import org.openhab.core.io.websocket.event.EventProcessingException;
|
||||
import org.openhab.core.io.websocket.event.ItemEventUtility;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.events.ItemEvent;
|
||||
|
||||
Reference in New Issue
Block a user