Merge branch 'etc/logging' into 'main'
Logging for flow of the data exchange between client/server Closes #37 See merge request cse1105/2025-2026/teams/csep-team-76!42
This commit is contained in:
commit
1c9ffb9d13
12 changed files with 85 additions and 22 deletions
|
|
@ -118,6 +118,18 @@
|
|||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
<version>2.0.17</version> <!-- use matching version for your SLF4J -->
|
||||
</dependency>
|
||||
<!-- Logback Classic logger implementation -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.20</version> <!-- or latest compatible version -->
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Long, Recipe> 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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
* <code>getLocaleString(String)</code> function is available.
|
||||
*/
|
||||
public class LangSelectMenuCtrl implements LocaleAware {
|
||||
private final Logger logger = Logger.getLogger(LangSelectMenuCtrl.class.getName());
|
||||
public ComboBox<String> 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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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!");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<List<Recipe>>() {
|
||||
});// JSON string-> List<Recipe> (Jackson)
|
||||
List<Recipe> list = objectMapper.readValue(response.body(), new TypeReference<List<Recipe>>() {
|
||||
});
|
||||
logger.info("Received response from server: " + list);
|
||||
return list; // JSON string-> List<Recipe> (Jackson)
|
||||
}
|
||||
|
||||
public List<Recipe> getRecipesFiltered(String filter) throws IOException, InterruptedException {
|
||||
|
|
|
|||
|
|
@ -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<ID, Value> {
|
||||
private Logger logger = Logger.getLogger(WebSocketDataService.class.getName());
|
||||
public WebSocketDataService() {
|
||||
|
||||
}
|
||||
|
|
@ -49,7 +51,11 @@ public class WebSocketDataService<ID, Value> {
|
|||
*/
|
||||
public boolean add(ID id, Consumer<Value> onComplete) {
|
||||
CompletableFuture<Value> 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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -158,12 +158,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")
|
||||
|
|
|
|||
|
|
@ -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<Recipe> 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<List<Recipe>> getRecipes(@RequestParam Optional<Integer> 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<Recipe> 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<Recipe> 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<Boolean> deleteRecipe(@PathVariable Long id) {
|
||||
logger.info("DELETE /recipe/" + id + " called.");
|
||||
if (!recipeService.delete(id)) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue