From 0cd1a00c98ec712e49b35e2a048bc1ecd640061a Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 8 Jan 2026 16:06:54 +0100 Subject: [PATCH 1/4] chore(logging): add slf4j and JUL bridge deps --- client/pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/pom.xml b/client/pom.xml index 3bd793a..509f836 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -118,6 +118,18 @@ compile + + org.slf4j + jul-to-slf4j + 2.0.17 + + + + ch.qos.logback + logback-classic + 1.5.20 + + From e91d6355f0051da7fa0b523a7ea197439efac9f3 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 8 Jan 2026 16:32:59 +0100 Subject: [PATCH 2/4] feat(client/logging): client-side info logging --- client/src/main/java/client/Main.java | 9 ++++++++ .../client/scenes/FoodpalApplicationCtrl.java | 21 +++++++++++++++---- .../client/scenes/LangSelectMenuCtrl.java | 3 +++ .../scenes/recipe/RecipeDetailCtrl.java | 2 +- .../main/java/client/utils/ConfigService.java | 7 +++++-- .../main/java/client/utils/ServerUtils.java | 9 +++++--- .../client/utils/WebSocketDataService.java | 8 ++++++- .../java/client/utils/WebSocketUtils.java | 10 ++++----- 8 files changed, 53 insertions(+), 16 deletions(-) diff --git a/client/src/main/java/client/Main.java b/client/src/main/java/client/Main.java index a0bd716..0c22fda 100644 --- a/client/src/main/java/client/Main.java +++ b/client/src/main/java/client/Main.java @@ -15,8 +15,17 @@ */ package client; +import org.slf4j.bridge.SLF4JBridgeHandler; + public class Main { + static { + // Choose SLF4J Logger (Spring Boot) for JavaFX. + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + } public static void main(String[] args){ + + UI.launch(UI.class, args); } } \ No newline at end of file diff --git a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java index 2453ccd..cdbd565 100644 --- a/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java +++ b/client/src/main/java/client/scenes/FoodpalApplicationCtrl.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.logging.Logger; import java.util.stream.Collectors; import client.exception.InvalidModificationException; @@ -45,7 +46,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { private final WebSocketUtils webSocketUtils; private final LocaleManager localeManager; private final WebSocketDataService dataService; - + private final Logger logger = Logger.getLogger(FoodpalApplicationCtrl.class.getName()); @FXML private RecipeDetailCtrl recipeDetailController; @@ -93,24 +94,31 @@ public class FoodpalApplicationCtrl implements LocaleAware { this.configService = configService; this.dataService = recipeDataService; setupDataService(); + logger.info("WebSocket processor initialized."); initializeWebSocket(); + logger.info("WebSocket connection handler initialized."); + logger.info("Main application controller initialized."); } private void setupDataService() { dataService.setMessageParser((msg) -> switch (msg) { case CreateRecipeMessage _ -> (m) -> { CreateRecipeMessage crm = (CreateRecipeMessage) m; + logger.info("Server informs us of creation of recipe: " + crm.getRecipe()); return handleCreateRecipeMessage(crm); }; case UpdateRecipeMessage _ -> (m) -> { UpdateRecipeMessage urm = (UpdateRecipeMessage) m; + logger.info("Server informs us of update for recipe: " + urm.getRecipe()); return handleUpdateRecipeMessage(urm); }; case DeleteRecipeMessage _ -> (m) -> { DeleteRecipeMessage drm = (DeleteRecipeMessage) m; + logger.info("Server informs us of the deletion of recipe with ID: " + drm.getRecipeId()); return handleDeleteRecipeMessage(drm); }; case FavouriteRecipeMessage _ -> (m) -> { FavouriteRecipeMessage frm = (FavouriteRecipeMessage) m; + logger.info("Server informs us of a favourite recipe being deleted: " + frm.getRecipeId()); return handleFavouriteRecipeMessage(frm); }; default -> throw new IllegalStateException("Unexpected value: " + msg); @@ -185,7 +193,7 @@ public class FoodpalApplicationCtrl implements LocaleAware { this.recipeList.getItems().setAll(recipes); - System.out.println("Search returned " + recipes.size() + " recipes."); + logger.info("Search returned " + recipes.size() + " recipes."); // Restore selection, if possible if (newIndex != -1) { @@ -263,7 +271,9 @@ public class FoodpalApplicationCtrl implements LocaleAware { recipes = server.getRecipesFiltered(searchBarController.getFilter()); } catch (IOException | InterruptedException e) { recipes = Collections.emptyList(); - System.err.println("Failed to load recipes: " + e.getMessage()); + String msg = "Failed to load recipes: " + e.getMessage(); + logger.severe(msg); + printError(msg); } allRecipes = new ArrayList<>(recipes); @@ -295,7 +305,10 @@ public class FoodpalApplicationCtrl implements LocaleAware { dataService.add(newRecipe.getId(), recipe -> { this.recipeList.getSelectionModel().select(recipe); openSelectedRecipe(); - Platform.runLater(() -> this.recipeDetailController.editRecipeTitle()); + Platform.runLater(() -> { + logger.info("Focused recipe title edit box."); + this.recipeDetailController.editRecipeTitle(); + }); }); recipeList.refresh(); } catch (IOException | InterruptedException e) { diff --git a/client/src/main/java/client/scenes/LangSelectMenuCtrl.java b/client/src/main/java/client/scenes/LangSelectMenuCtrl.java index 1df3e54..41df1bd 100644 --- a/client/src/main/java/client/scenes/LangSelectMenuCtrl.java +++ b/client/src/main/java/client/scenes/LangSelectMenuCtrl.java @@ -15,6 +15,7 @@ import javafx.util.StringConverter; import java.io.InputStream; import java.util.Locale; +import java.util.logging.Logger; /** * The language selection menu controller. @@ -22,6 +23,7 @@ import java.util.Locale; * getLocaleString(String) function is available. */ public class LangSelectMenuCtrl implements LocaleAware { + private final Logger logger = Logger.getLogger(LangSelectMenuCtrl.class.getName()); public ComboBox langSelectMenu; private final LocaleManager manager; @@ -37,6 +39,7 @@ public class LangSelectMenuCtrl implements LocaleAware { @FXML private void switchLocale(ActionEvent event) { String lang = langSelectMenu.getSelectionModel().getSelectedItem(); + logger.info("Switching locale to " + lang); manager.setLocale(Locale.of(lang)); } diff --git a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java index 0675407..716a128 100644 --- a/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java +++ b/client/src/main/java/client/scenes/recipe/RecipeDetailCtrl.java @@ -207,7 +207,7 @@ public class RecipeDetailCtrl implements LocaleAware { try { server.updateRecipe(this.recipe); - this.refresh(); + // this.refresh(); } catch (IOException | InterruptedException e) { // throw a nice blanket UpdateException throw new UpdateException("Error occurred when updating recipe name!"); diff --git a/client/src/main/java/client/utils/ConfigService.java b/client/src/main/java/client/utils/ConfigService.java index d1d70c6..54c7036 100644 --- a/client/src/main/java/client/utils/ConfigService.java +++ b/client/src/main/java/client/utils/ConfigService.java @@ -7,10 +7,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.file.Path; import java.io.File; import java.io.IOException; +import java.util.logging.Logger; public class ConfigService { private final Path configPath; private final ObjectMapper mapper = new ObjectMapper(); + private final Logger logger = Logger.getLogger(ConfigService.class.getName()); private Config config; @@ -70,9 +72,10 @@ public class ConfigService { try { File file = configPath.toFile(); // file is the config file here mapper.writeValue(file, config); // here we edit the value of the file using config - + logger.info("Config saved to " + file.getAbsolutePath()); } - catch (Exception e){ + catch (Exception e) { + logger.severe("Something bad happened while saving the config: " + e.getMessage()); throw new RuntimeException(e); } } diff --git a/client/src/main/java/client/utils/ServerUtils.java b/client/src/main/java/client/utils/ServerUtils.java index cb91e76..9c89508 100644 --- a/client/src/main/java/client/utils/ServerUtils.java +++ b/client/src/main/java/client/utils/ServerUtils.java @@ -18,12 +18,14 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; public class ServerUtils { private static final String SERVER = "http://localhost:8080/api"; + private Logger logger = Logger.getLogger(ServerUtils.class.getName()); private final HttpClient client; private final ObjectMapper objectMapper = new ObjectMapper(); private final int statusOK = 200; @@ -48,9 +50,10 @@ public class ServerUtils { throw new IOException("No recipe to get. Server responds with " + response.body()); } - - return objectMapper.readValue(response.body(), new TypeReference>() { - });// JSON string-> List (Jackson) + List list = objectMapper.readValue(response.body(), new TypeReference>() { + }); + logger.info("Received response from server: " + list); + return list; // JSON string-> List (Jackson) } public List getRecipesFiltered(String filter) throws IOException, InterruptedException { diff --git a/client/src/main/java/client/utils/WebSocketDataService.java b/client/src/main/java/client/utils/WebSocketDataService.java index 659d91b..52d0b1f 100644 --- a/client/src/main/java/client/utils/WebSocketDataService.java +++ b/client/src/main/java/client/utils/WebSocketDataService.java @@ -10,8 +10,10 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; +import java.util.logging.Logger; public class WebSocketDataService { + private Logger logger = Logger.getLogger(WebSocketDataService.class.getName()); public WebSocketDataService() { } @@ -49,7 +51,11 @@ public class WebSocketDataService { */ public boolean add(ID id, Consumer onComplete) { CompletableFuture future = new CompletableFuture<>(); - future.thenAccept(onComplete.andThen(_ -> pendingRegister.remove(id))); + future.thenAccept(onComplete.andThen(_ -> { + logger.info("Item " + id + " resolved. Removing from pending register."); + pendingRegister.remove(id); + })); + logger.info("Item " + id + " pending propagation. Adding to pending register."); return pendingRegister.putIfAbsent(id, future) == null; } public boolean add(ID id) { diff --git a/client/src/main/java/client/utils/WebSocketUtils.java b/client/src/main/java/client/utils/WebSocketUtils.java index ed16b37..bb070be 100644 --- a/client/src/main/java/client/utils/WebSocketUtils.java +++ b/client/src/main/java/client/utils/WebSocketUtils.java @@ -14,11 +14,13 @@ import java.lang.reflect.Type; import java.util.function.Consumer; import javax.annotation.Nullable; import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; public class WebSocketUtils { private static final String WS_URL = "ws://localhost:8080/updates"; private WebSocketStompClient stompClient; private StompSession stompSession; + private Logger logger = Logger.getLogger(WebSocketUtils.class.getName()); /** * Connect to the websocket server. @@ -36,21 +38,19 @@ public class WebSocketUtils { @Override public void afterConnected(StompSession session, StompHeaders connectedHeaders) { stompSession = session; - System.out.println("WebSocket connected: " + session.getSessionId()); + logger.info("WebSocket connected with session ID: " + session.getSessionId()); } @Override public void handleException(StompSession session, @Nullable StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) { - System.err.println("STOMP error: " + exception.getMessage()); - exception.printStackTrace(); + logger.severe("STOMP error: " + exception.getMessage()); } @Override public void handleTransportError(StompSession session, Throwable exception) { - System.err.println("STOMP transport error: " + exception.getMessage()); - exception.printStackTrace(); + logger.severe("STOMP transport error: " + exception.getMessage()); } } ); From 841fe75fad5ada3e13fb6fd60b3adbe1c686aa30 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 8 Jan 2026 16:33:08 +0100 Subject: [PATCH 3/4] chore(commons/logging): descriptive toString for models --- commons/src/main/java/commons/Ingredient.java | 8 ++++++++ commons/src/main/java/commons/Recipe.java | 10 ++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/commons/src/main/java/commons/Ingredient.java b/commons/src/main/java/commons/Ingredient.java index dd93aab..5704b74 100644 --- a/commons/src/main/java/commons/Ingredient.java +++ b/commons/src/main/java/commons/Ingredient.java @@ -103,6 +103,14 @@ public class Ingredient { public int hashCode() { return Objects.hash(id, name, proteinPer100g, fatPer100g, carbsPer100g); } + + @Override + public String toString() { + return "Ingredient " + id + " - " + name + + "= P:" + proteinPer100g + + "/F:" + fatPer100g + + "/C:" + carbsPer100g + " per 100g"; + } } diff --git a/commons/src/main/java/commons/Recipe.java b/commons/src/main/java/commons/Recipe.java index 1ae1f83..5c9b19e 100644 --- a/commons/src/main/java/commons/Recipe.java +++ b/commons/src/main/java/commons/Recipe.java @@ -157,12 +157,10 @@ public class Recipe { @Override public String toString() { - return "Recipe{" + - "id=" + id + - ", name='" + name + '\'' + - ", ingredientsCount=" + ingredients.size() + - ", preparationStepsCount=" + preparationSteps.size() + - "}"; + return "Recipe " + id + + " - " + name + + ": " + ingredients.size() + " ingredients / " + + preparationSteps.size() + " steps"; } @SuppressWarnings("unused") From 9787584d970007763a18783d00f0350bcd754cfd Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Thu, 8 Jan 2026 16:33:19 +0100 Subject: [PATCH 4/4] feat(server/logging): endpoint calls logging --- server/src/main/java/server/api/RecipeController.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/src/main/java/server/api/RecipeController.java b/server/src/main/java/server/api/RecipeController.java index 37a2a78..e2143e7 100644 --- a/server/src/main/java/server/api/RecipeController.java +++ b/server/src/main/java/server/api/RecipeController.java @@ -24,16 +24,19 @@ import server.service.RecipeService; import java.util.List; import java.util.Optional; +import java.util.logging.Logger; @RestController @RequestMapping("/api") public class RecipeController { + private static final Logger logger = Logger.getLogger(RecipeController.class.getName()); private final SimpMessagingTemplate messagingTemplate; private final RecipeService recipeService; public RecipeController(RecipeService recipeService, SimpMessagingTemplate messagingTemplate) { this.recipeService = recipeService; this.messagingTemplate = messagingTemplate; + logger.info("Initialized controller."); } /** @@ -46,6 +49,7 @@ public class RecipeController { */ @GetMapping("/recipe/{id}") public ResponseEntity getRecipe(@PathVariable Long id) { + logger.info("GET /recipe/" + id + " called."); return recipeService.findById(id) .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); @@ -60,6 +64,7 @@ public class RecipeController { */ @GetMapping("/recipes") public ResponseEntity> getRecipes(@RequestParam Optional limit) { + logger.info("GET /recipes called."); return ResponseEntity.ok( // Choose the right overload. One has a limit, other doesn't. limit.map(recipeService::findAll).orElseGet(recipeService::findAll) @@ -76,6 +81,7 @@ public class RecipeController { */ @PostMapping("/recipe/{id}") public ResponseEntity updateRecipe(@PathVariable Long id, @RequestBody Recipe recipe) { + logger.info("POST /recipe/" + id + " called."); return recipeService.update(id, recipe) .map(saved -> { messagingTemplate.convertAndSend(Topics.RECIPES, new UpdateRecipeMessage(saved)); // Send to WS. @@ -96,6 +102,7 @@ public class RecipeController { */ @PutMapping("/recipe/new") public ResponseEntity createRecipe(@RequestBody Recipe recipe) { + logger.info("POST /recipe/new called."); return recipeService.create(recipe) .map(saved -> { messagingTemplate.convertAndSend(Topics.RECIPES, new CreateRecipeMessage(saved)); // Send to WS. @@ -114,6 +121,7 @@ public class RecipeController { */ @DeleteMapping("/recipe/{id}") public ResponseEntity deleteRecipe(@PathVariable Long id) { + logger.info("DELETE /recipe/" + id + " called."); if (!recipeService.delete(id)) { return ResponseEntity.badRequest().build(); }