package dev.dinauer.inspect.log; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import dev.dinauer.utils.ClientProvider; import io.fabric8.kubernetes.client.dsl.LogWatch; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.websocket.OnClose; import jakarta.websocket.OnOpen; import jakarta.websocket.Session; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import org.eclipse.microprofile.context.ManagedExecutor; import org.jboss.logging.Logger; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.Future; @ServerEndpoint("/logs/{namespace}/{name}") @ApplicationScoped public class LogWebsocket { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper().registerModule(new JavaTimeModule()).disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); private final Map sessions = new HashMap<>(); @Inject Logger LOG; @Inject ClientProvider clientProvider; @Inject ManagedExecutor executor; @OnOpen public void onOpen(Session session, @PathParam("namespace") String namespace, @PathParam("name") String name) { executor.submit(() -> { List existingLogs = Arrays.stream(clientProvider.getClient().pods().inNamespace(namespace).withName(name).usingTimestamps().tailingLines(200).getLog().split("\n")).toList(); send(session, toLog(existingLogs)); LogWatch watch = clientProvider.getClient().pods().inNamespace(namespace).withName(name).usingTimestamps().tailingLines(0).watchLog(); sessions.put(session, watch); try(BufferedReader reader = new BufferedReader(new InputStreamReader(watch.getOutput()))) { String line; while ((line = reader.readLine()) != null && !Thread.currentThread().isInterrupted()) { send(session, toLog(List.of(line))); } LOG.info("Ended"); } catch (Exception e) { LOG.errorf("Error reading output of log watch: %s", e.getMessage()); } }); } @OnClose public void onClose(Session session) throws IOException { LogWatch watch = sessions.remove(session); if (watch != null) { watch.close(); } session.close(); } private void send(Session session, List logs) { try { session.getAsyncRemote().sendText(OBJECT_MAPPER.writeValueAsString(logs)); } catch (Exception e) { LOG.errorf("Error sending logs to frontend via websocket: %s", e.getMessage()); } } private List toLog(List logs) { List result = new ArrayList<>(); for (String log : logs) { int indexFirstSpace = log.indexOf(" "); if (indexFirstSpace != -1) { String timestampRaw = log.substring(0, indexFirstSpace); String message = log.substring(indexFirstSpace).trim(); try { result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message)); } catch (Exception e) { LOG.errorf("Error parsing log: %s", e.getMessage()); } } } return result; } }