Add req/res transaction logging

This commit is contained in:
Braydon 2024-04-06 16:11:03 -04:00
parent fd44a0b25c
commit 035b86920a
5 changed files with 148 additions and 8 deletions

@ -0,0 +1,44 @@
package me.braydon.mc.common;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
/**
* @author Braydon
*/
@UtilityClass
public final class IPUtils {
private static final String[] IP_HEADERS = new String[] {
"CF-Connecting-IP",
"X-Forwarded-For"
};
/**
* Get the real IP from the given request.
*
* @param request the request
* @return the real IP
*/
@NonNull
public static String getRealIp(@NonNull HttpServletRequest request) {
String ip = request.getRemoteAddr();
for (String headerName : IP_HEADERS) {
String header = request.getHeader(headerName);
if (header == null) {
continue;
}
if (!header.contains(",")) { // Handle single IP
ip = header;
break;
}
// Handle multiple IPs
String[] ips = header.split(",");
for (String ipHeader : ips) {
ip = ipHeader;
break;
}
}
return ip;
}
}

@ -42,6 +42,6 @@ public final class PlayerController {
@GetMapping("/{query}")
@ResponseBody
public ResponseEntity<CachedPlayer> getPlayer(@PathVariable @NonNull String query) throws BadRequestException, ResourceNotFoundException {
return ResponseEntity.ofNullable(mojangService.getPlayer(query));
return ResponseEntity.ofNullable(mojangService.getPlayer(query, false));
}
}

@ -0,0 +1,84 @@
package me.braydon.mc.log;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import me.braydon.mc.common.IPUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* Responsible for logging request and
* response transactions to the terminal.
*
* @author Braydon
*/
@ControllerAdvice
@Slf4j(topic = "Req/Res Transaction")
public class TransactionLogger implements ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NonNull ServerHttpRequest rawRequest,
@NonNull ServerHttpResponse rawResponse) {
HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest();
HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse();
// Get the request ip ip
String ip = IPUtils.getRealIp(request);
// Getting params
Map<String, String> params = new HashMap<>();
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), Arrays.toString(entry.getValue()));
}
// Getting headers
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
// Log the request
log.info(String.format("[Req] %s | %s | '%s', params=%s, headers=%s",
request.getMethod(),
ip,
request.getRequestURI(),
params,
headers
));
// Getting response headers
headers = new HashMap<>();
for (String headerName : response.getHeaderNames()) {
headers.put(headerName, response.getHeader(headerName));
}
// Log the response
log.info(String.format("[Res] %s, headers=%s",
response.getStatus(),
headers
));
return body;
}
@Override
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
}

@ -0,0 +1,7 @@
package me.braydon.mc.model;
/**
* @author Braydon
*/
public final class Skin {
}

@ -61,12 +61,13 @@ public final class MojangService {
* </p>
*
* @param query the query to search for the player by
* @param bypassCache should the cache be bypassed?
* @return the player
* @throws BadRequestException if the UUID is malformed
* @throws ResourceNotFoundException if the player is not found
*/
@NonNull
public CachedPlayer getPlayer(@NonNull String query) throws BadRequestException, ResourceNotFoundException {
public CachedPlayer getPlayer(@NonNull String query, boolean bypassCache) throws BadRequestException, ResourceNotFoundException {
log.info("Requesting player with query: {}", query);
UUID uuid; // The player UUID to lookup
@ -84,10 +85,12 @@ public final class MojangService {
}
// Check the cache for the player
Optional<CachedPlayer> cached = playerCache.findById(uuid);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found player in cache: {}", uuid);
return cached.get();
if (!bypassCache) {
Optional<CachedPlayer> cached = playerCache.findById(uuid);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found player in cache: {}", uuid);
return cached.get();
}
}
// Send a request to Mojang requesting
@ -105,8 +108,10 @@ public final class MojangService {
profileActions.length == 0 ? null : profileActions,
System.currentTimeMillis()
);
playerCache.save(player);
log.info("Cached player: {}", uuid);
if (!bypassCache) { // Store in the cache
playerCache.save(player);
log.info("Cached player: {}", uuid);
}
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
return player;