From 063e51695ba8acc47f04e2cd28ca2027edf27319 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 24 Jun 2026 18:08:19 +0200 Subject: [PATCH 1/8] feat(logger): include protection logger --- backend/cmd/orchestrator.go | 6 ++++-- backend/pkg/logger/protection/logger.go | 26 +++++++------------------ backend/pkg/vehicle/notification.go | 5 +++-- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/backend/cmd/orchestrator.go b/backend/cmd/orchestrator.go index 53e9f6cac..96e882011 100644 --- a/backend/cmd/orchestrator.go +++ b/backend/cmd/orchestrator.go @@ -15,6 +15,7 @@ import ( "github.com/HyperloopUPV-H8/h9-backend/pkg/logger" data_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/data" order_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/order" + protection_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/protection" trace "github.com/rs/zerolog/log" ) @@ -137,8 +138,9 @@ func createLookupTables( func setUpLogger(config config.Config, commitHash string) (*logger.Logger, abstraction.SubloggersMap, error) { var subloggers = abstraction.SubloggersMap{ - data_logger.Name: data_logger.NewLogger(), - order_logger.Name: order_logger.NewLogger(), + data_logger.Name: data_logger.NewLogger(), + protection_logger.Name: protection_logger.NewLogger(), + order_logger.Name: order_logger.NewLogger(), } err := logger.ConfigureLogger(config.Logging.TimeUnit, config.Logging.LoggingPath, commitHash) diff --git a/backend/pkg/logger/protection/logger.go b/backend/pkg/logger/protection/logger.go index 17b81d762..30370f588 100644 --- a/backend/pkg/logger/protection/logger.go +++ b/backend/pkg/logger/protection/logger.go @@ -19,17 +19,10 @@ const ( ) type Logger struct { - - // embed the base logger *loggerbase.BaseLogger - // An atomic boolean is used in order to use CompareAndSwap in the Start and Stop methods - fileLock *sync.Mutex - // saveFiles is a map that contains the file of each info packet + fileLock *sync.Mutex saveFiles map[abstraction.BoardId]*file.CSV - // BoardNames is a map that contains the common name of each board - boardNames map[abstraction.BoardId]string - // save the starting time of the logger in Unix microseconds in order to log relative timestamps } // Record is a struct that implements the abstraction.LoggerRecord interface @@ -43,14 +36,12 @@ type Record struct { func (*Record) Name() abstraction.LoggerName { return Name } -func NewLogger(boardMap map[abstraction.BoardId]string) *Logger { +func NewLogger() *Logger { - fmt.Print("ssfs") return &Logger{ BaseLogger: loggerbase.NewBaseLogger(Name), fileLock: &sync.Mutex{}, saveFiles: make(map[abstraction.BoardId]*file.CSV), - boardNames: boardMap, } } @@ -63,6 +54,7 @@ func (sublogger *Logger) PushRecord(record abstraction.LoggerRecord) error { } infoRecord, ok := record.(*Record) + if !ok { return logger.ErrWrongRecordType{ Name: Name, @@ -72,7 +64,7 @@ func (sublogger *Logger) PushRecord(record abstraction.LoggerRecord) error { } } - saveFile, err := sublogger.getFile(infoRecord.BoardId) + saveFile, err := sublogger.getFile(infoRecord.BoardId, infoRecord.From) if err != nil { return err } @@ -92,7 +84,7 @@ func (sublogger *Logger) PushRecord(record abstraction.LoggerRecord) error { return err } -func (sublogger *Logger) getFile(boardId abstraction.BoardId) (*file.CSV, error) { +func (sublogger *Logger) getFile(boardId abstraction.BoardId, boardName string) (*file.CSV, error) { sublogger.fileLock.Lock() defer sublogger.fileLock.Unlock() @@ -101,7 +93,7 @@ func (sublogger *Logger) getFile(boardId abstraction.BoardId) (*file.CSV, error) return valueFile, nil } - valueFileRaw, err := sublogger.createFile(boardId) + valueFileRaw, err := sublogger.createFile(boardId, boardName) sublogger.saveFiles[boardId] = file.NewCSV(valueFileRaw) return sublogger.saveFiles[boardId], err @@ -109,11 +101,7 @@ func (sublogger *Logger) getFile(boardId abstraction.BoardId) (*file.CSV, error) // override createFile from BaseLogger to add specific path // and filename structure -func (sublogger *Logger) createFile(boardId abstraction.BoardId) (*os.File, error) { - boardName, ok := sublogger.boardNames[boardId] - if !ok { - boardName = fmt.Sprint(boardId) - } +func (sublogger *Logger) createFile(boardId abstraction.BoardId, boardName string) (*os.File, error) { filename := path.Join( "logger", diff --git a/backend/pkg/vehicle/notification.go b/backend/pkg/vehicle/notification.go index 4bf0d9d05..52e4a9948 100644 --- a/backend/pkg/vehicle/notification.go +++ b/backend/pkg/vehicle/notification.go @@ -82,16 +82,17 @@ func (vehicle *Vehicle) handlePacketNotification(notification transport.PacketNo } case *protection.Packet: - boardId := vehicle.ipToBoardId[strings.Split(notification.From, ":")[0]] + boardID := vehicle.ipToBoardId[strings.Split(notification.From, ":")[0]] err := vehicle.broker.Push(message_topic.Push(p, vehicle.idToBoardName[p.Id()])) if err != nil { vehicle.trace.Error().Stack().Err(err).Msg("broker push") return errors.Join(fmt.Errorf("update protection to frontend (%s protection with id %d and kind %d from %s to %s)", p.Severity(), p.Id(), p.Kind, notification.From, notification.To), err) } + // Log protection err = vehicle.logger.PushRecord(&protection_logger.Record{ Packet: p, - BoardId: boardId, + BoardId: boardID, From: notification.From, To: notification.To, Timestamp: notification.Timestamp, From 2f7e22af40a937acccdcbcc900dde73830856eda Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 24 Jun 2026 18:35:52 +0200 Subject: [PATCH 2/8] fix(logging-path): correct protection logger --- backend/pkg/logger/protection/logger.go | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/pkg/logger/protection/logger.go b/backend/pkg/logger/protection/logger.go index 30370f588..a92148515 100644 --- a/backend/pkg/logger/protection/logger.go +++ b/backend/pkg/logger/protection/logger.go @@ -104,7 +104,6 @@ func (sublogger *Logger) getFile(boardId abstraction.BoardId, boardName string) func (sublogger *Logger) createFile(boardId abstraction.BoardId, boardName string) (*os.File, error) { filename := path.Join( - "logger", logger.Timestamp.Format(logger.TimestampFormat), "protections", fmt.Sprintf("%s.csv", boardName), From 8aaabd7265b651a892efc7aea6c4f978510c5f67 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 24 Jun 2026 19:42:39 +0200 Subject: [PATCH 3/8] fix(backend): change route log --- backend/cmd/main.go | 26 ++++++++++++++++---------- backend/cmd/orchestrator.go | 8 ++------ backend/pkg/logger/logger.go | 7 +++++-- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 1cdbafefc..5ce0e5126 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -9,6 +9,7 @@ import ( "github.com/HyperloopUPV-H8/h9-backend/internal/flags" "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/internal/update_factory" + "github.com/HyperloopUPV-H8/h9-backend/pkg/logger" tracelogger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/trace" vehicle_models "github.com/HyperloopUPV-H8/h9-backend/internal/vehicle/models" @@ -34,12 +35,6 @@ func main() { flags.Init() handleVersionFlag() - // Configure trace - traceFile := tracelogger.InitTrace(flags.TraceLevel) - if traceFile != nil { - defer traceFile.Close() - } - // Set use to all available CPUs and setup CPU profiling if enabled cleanup := setupRuntimeCPU() defer cleanup() @@ -50,12 +45,26 @@ func main() { trace.Fatal().Err(err).Msg("error unmarshaling toml file") } + // Configure BasePath before InitTrace and NewADJ so all "others" files land in the right place + if err := logger.ConfigureLogger(config.Logging.TimeUnit, config.Logging.LoggingPath, ""); err != nil { + trace.Fatal().Err(err).Msg("configuring logger") + } + + // Configure trace + traceFile := tracelogger.InitTrace(flags.TraceLevel) + if traceFile != nil { + defer traceFile.Close() + } + // <--- ADJ ---> adj, err := adj_module.NewADJ(config.Adj) if err != nil { trace.Fatal().Err(err).Msg("setting up ADJ") } + // Now that we have the commit hash, update it in the logger + logger.CommitHash = adj.Commit + // <--- pod data ---> podData, err := pod_data.NewPodData(adj.Boards, adj.Info.Units) if err != nil { @@ -75,10 +84,7 @@ func main() { updateFactory := update_factory.NewFactory(boardToPackets) // <--- logger ---> - loggerHandler, subloggers, err := setUpLogger(config, adj.Commit) - if err != nil { - trace.Fatal().Err(err).Msg("setting up logger") - } + loggerHandler, subloggers := setUpLogger() // <-- connections & upgrader --> connections := make(chan *websocket.Client) diff --git a/backend/cmd/orchestrator.go b/backend/cmd/orchestrator.go index 96e882011..0c804356d 100644 --- a/backend/cmd/orchestrator.go +++ b/backend/cmd/orchestrator.go @@ -7,7 +7,6 @@ import ( "runtime/pprof" "strings" - "github.com/HyperloopUPV-H8/h9-backend/internal/config" "github.com/HyperloopUPV-H8/h9-backend/internal/flags" "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" @@ -135,7 +134,7 @@ func createLookupTables( createBoardToPackets(podData) } -func setUpLogger(config config.Config, commitHash string) (*logger.Logger, abstraction.SubloggersMap, error) { +func setUpLogger() (*logger.Logger, abstraction.SubloggersMap) { var subloggers = abstraction.SubloggersMap{ data_logger.Name: data_logger.NewLogger(), @@ -143,10 +142,7 @@ func setUpLogger(config config.Config, commitHash string) (*logger.Logger, abstr order_logger.Name: order_logger.NewLogger(), } - err := logger.ConfigureLogger(config.Logging.TimeUnit, config.Logging.LoggingPath, commitHash) - loggerHandler := logger.NewLogger(subloggers, trace.Logger) - return loggerHandler, subloggers, err - + return loggerHandler, subloggers } diff --git a/backend/pkg/logger/logger.go b/backend/pkg/logger/logger.go index 511d2e1db..ad411be13 100644 --- a/backend/pkg/logger/logger.go +++ b/backend/pkg/logger/logger.go @@ -207,7 +207,7 @@ type LoggerSettings struct { } // WriteLoggerSettings writes the logger settings to a JSON file in the logger directory -func WriteLoggerSettings(path string) error { +func WriteLoggerSettings(filePath string) error { settings := LoggerSettings{ AdjCommitHash: CommitHash, TimeUnit: TimestampUnit, @@ -219,6 +219,9 @@ func WriteLoggerSettings(path string) error { return err } - return os.WriteFile(path, settingsBytes, 0644) + if err := os.MkdirAll(path.Dir(filePath), os.ModePerm); err != nil { + return err + } + return os.WriteFile(filePath, settingsBytes, 0644) } From a37f60a49e8db16122e52d18731acb7b42fa7161 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 24 Jun 2026 23:40:31 +0200 Subject: [PATCH 4/8] fix(electron-app): restart app working fine --- electron-app/preload.js | 2 ++ electron-app/src/ipc/handlers.js | 21 ++++++++++++++++++- electron-app/src/processes/backend.js | 9 +------- .../testing-view/src/components/Error.tsx | 6 +++++- frontend/testing-view/src/vite-end.d.ts | 1 + 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/electron-app/preload.js b/electron-app/preload.js index e9fcb690d..962ee2149 100644 --- a/electron-app/preload.js +++ b/electron-app/preload.js @@ -40,6 +40,8 @@ contextBridge.exposeInMainWorld("electronAPI", { openFolder: (path) => ipcRenderer.invoke("open-folder", path), // Get the application version from the main process getAppVersion: () => ipcRenderer.invoke("get-app-version"), + // Restart the backend process and reload the renderer when ready + restartBackend: () => ipcRenderer.invoke("restart-backend"), // Set initial mode (used by mode selector renderer) setInitialMode: (mode) => { ipcRenderer.send("mode-selected", mode); diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index 8d40d9a8c..d72ca4aca 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -15,12 +15,16 @@ import { readConfig, writeConfig, } from "../config/configInstance.js"; -import { getBackendWorkingDir } from "../processes/backend.js"; +import { + getBackendWorkingDir, + restartBackend, +} from "../processes/backend.js"; import { logger } from "../utils/logger.js"; import { getCurrentView, getMainWindow, loadView, + reloadWindow, } from "../windows/mainWindow.js"; /** @@ -39,6 +43,21 @@ function setupIpcHandlers() { */ ipcMain.handle("get-current-view", () => getCurrentView()); + /** + * @event restart-backend + * @async + * @description Stops the backend process, restarts it, and reloads the renderer once ready. + */ + ipcMain.handle("restart-backend", async () => { + try { + await restartBackend(); + reloadWindow(); + } catch (error) { + logger.electron.error("Failed to restart backend:", error); + throw error; + } + }); + ipcMain.handle("get-app-version", () => app.getVersion()); /** diff --git a/electron-app/src/processes/backend.js b/electron-app/src/processes/backend.js index 64b51c838..b20d70df7 100644 --- a/electron-app/src/processes/backend.js +++ b/electron-app/src/processes/backend.js @@ -242,20 +242,13 @@ async function stopBackend() { * restartBackend(); */ async function restartBackend() { - // Stop current process first await stopBackend(); - - if (localBackendProcess.stdin) { - localBackendProcess.stdin.end(); - } - - // Start a new process try { await startBackend(); logger.electron.info("Backend restarted successfully"); } catch (error) { logger.electron.error("Failed to restart backend:", error); - throw error; // Let the IPC handler know it failed + throw error; } } diff --git a/frontend/testing-view/src/components/Error.tsx b/frontend/testing-view/src/components/Error.tsx index fbec77e4b..d71687adf 100644 --- a/frontend/testing-view/src/components/Error.tsx +++ b/frontend/testing-view/src/components/Error.tsx @@ -23,7 +23,11 @@ export const Error = ({ error: propError, componentStack }: ErrorProps) => { const [showDetails, setShowDetails] = useState(false); const handleReload = () => { - window.location.reload(); + if (window.electronAPI) { + window.electronAPI.restartBackend(); + } else { + window.location.reload(); + } }; return ( diff --git a/frontend/testing-view/src/vite-end.d.ts b/frontend/testing-view/src/vite-end.d.ts index 28748e194..c92d01c51 100644 --- a/frontend/testing-view/src/vite-end.d.ts +++ b/frontend/testing-view/src/vite-end.d.ts @@ -8,6 +8,7 @@ interface ElectronAPI { importConfig: () => Promise; selectFolder: () => Promise; openFolder: (path: string) => Promise; + restartBackend: () => Promise; } declare global { From 7535be64b0238696c62bc0fa6824135bdbedf0b3 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Wed, 24 Jun 2026 23:56:49 +0200 Subject: [PATCH 5/8] fix(electron-app): restart app --- electron-app/src/ipc/handlers.js | 2 +- electron-app/src/processes/backend.js | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index d72ca4aca..135c44720 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -54,7 +54,7 @@ function setupIpcHandlers() { reloadWindow(); } catch (error) { logger.electron.error("Failed to restart backend:", error); - throw error; + dialog.showErrorBox("Restart Failed", `Could not restart backend:\n\n${error.message}`); } }); diff --git a/electron-app/src/processes/backend.js b/electron-app/src/processes/backend.js index b20d70df7..c314a4637 100644 --- a/electron-app/src/processes/backend.js +++ b/electron-app/src/processes/backend.js @@ -165,10 +165,17 @@ async function startBackend(logWindow = null) { } if (code === null || code === 0) { - logger.backend.warning("Backend closed before ready signal - likely port conflict or initialization error"); + let errorMessage = "Backend process closed before initialization completed"; + if (lastBackendError) { + const stripped = lastBackendError.replace(/\x1b\[[0-9;]*m/g, ""); + errorMessage += `\n\n${stripped}`; + lastBackendError = null; + } + logger.backend.warning(errorMessage); + dialog.showErrorBox("Backend Failed to Start", errorMessage); backendProcess = null; resolved = true; - return reject(new Error("Backend process closed before initialization completed")); + return reject(new Error(errorMessage)); } } @@ -243,6 +250,8 @@ async function stopBackend() { */ async function restartBackend() { await stopBackend(); + // Brief pause so the OS fully releases ports before the new process binds them + await new Promise((resolve) => setTimeout(resolve, 500)); try { await startBackend(); logger.electron.info("Backend restarted successfully"); From eb732e63cbbf71352365756b748d8eb3c2578a7e Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Thu, 25 Jun 2026 23:26:42 +0200 Subject: [PATCH 6/8] fix(backend): remove protection_message --- backend/protection_message.json | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 backend/protection_message.json diff --git a/backend/protection_message.json b/backend/protection_message.json deleted file mode 100644 index f65822bdf..000000000 --- a/backend/protection_message.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "boardId": 12, - "timestamp": { - "counter": 38923221, - "second": 32, - "minute": 12, - "hour": 8, - "day": 25, - "month": 7, - "year": 2023 - }, - "name": "VCELL1", - "protection": { - "type": 4412323, - "data": { - "value": 3.3, - "boundary_1": 2.5, - "boundary_2": 3.2 - } - } -} From ebdd27e617513afcb775b5e9bfda5061e5df8399 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 26 Jun 2026 17:56:15 +0200 Subject: [PATCH 7/8] fix(testing-view): add cooldown --- frontend/testing-view/src/components/Error.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/testing-view/src/components/Error.tsx b/frontend/testing-view/src/components/Error.tsx index d71687adf..4e097e2fb 100644 --- a/frontend/testing-view/src/components/Error.tsx +++ b/frontend/testing-view/src/components/Error.tsx @@ -1,9 +1,11 @@ import { Button } from "@workspace/ui"; import { RefreshCw, Terminal } from "@workspace/ui/icons"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import errorGif from "../assets/error.gif"; import { useStore } from "../store/store"; +const RELOAD_COOLDOWN = 8; + interface ErrorProps { /** Optional error to display. Can be null or undefined. In this case component will show default error message */ error?: Error | null; @@ -21,6 +23,13 @@ export const Error = ({ error: propError, componentStack }: ErrorProps) => { const storeError = useStore((s) => s.error); const error = propError || storeError; const [showDetails, setShowDetails] = useState(false); + const [countdown, setCountdown] = useState(RELOAD_COOLDOWN); + + useEffect(() => { + if (countdown <= 0) return; + const id = setTimeout(() => setCountdown((c) => c - 1), 1000); + return () => clearTimeout(id); + }, [countdown]); const handleReload = () => { if (window.electronAPI) { @@ -76,11 +85,12 @@ export const Error = ({ error: propError, componentStack }: ErrorProps) => {
{error?.stack && ( From 83214ad2ee5fa0b1ad9521967f182955d7e279a3 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 26 Jun 2026 18:14:06 +0200 Subject: [PATCH 8/8] fix(testing-view): cool down --- frontend/testing-view/src/components/Error.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/testing-view/src/components/Error.tsx b/frontend/testing-view/src/components/Error.tsx index 4e097e2fb..645015c39 100644 --- a/frontend/testing-view/src/components/Error.tsx +++ b/frontend/testing-view/src/components/Error.tsx @@ -32,6 +32,7 @@ export const Error = ({ error: propError, componentStack }: ErrorProps) => { }, [countdown]); const handleReload = () => { + setCountdown(RELOAD_COOLDOWN); if (window.electronAPI) { window.electronAPI.restartBackend(); } else {