Skip to content

Commit 678d787

Browse files
committed
WIP
1 parent 7f3bffa commit 678d787

File tree

3 files changed

+199
-228
lines changed

3 files changed

+199
-228
lines changed

src/main/java/io/apitally/common/RequestLogger.java

Lines changed: 118 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import java.util.Arrays;
99
import java.util.Deque;
1010
import java.util.List;
11-
import java.util.UUID;
1211
import java.util.concurrent.ConcurrentLinkedDeque;
1312
import java.util.concurrent.Executors;
1413
import java.util.concurrent.ScheduledExecutorService;
@@ -21,12 +20,14 @@
2120
import org.slf4j.Logger;
2221
import org.slf4j.LoggerFactory;
2322

23+
import com.fasterxml.jackson.databind.JsonNode;
2424
import com.fasterxml.jackson.databind.ObjectMapper;
2525
import com.fasterxml.jackson.databind.node.ObjectNode;
2626

2727
import io.apitally.common.dto.ExceptionDto;
2828
import io.apitally.common.dto.Header;
2929
import io.apitally.common.dto.Request;
30+
import io.apitally.common.dto.RequestLogItem;
3031
import io.apitally.common.dto.Response;
3132

3233
public class RequestLogger {
@@ -80,7 +81,7 @@ public class RequestLogger {
8081
private final RequestLoggingConfig config;
8182
private final ObjectMapper objectMapper;
8283
private final ReentrantLock lock;
83-
private final Deque<String> pendingWrites;
84+
private final Deque<RequestLogItem> pendingWrites;
8485
private final Deque<TempGzipFile> files;
8586
private TempGzipFile currentFile;
8687
private boolean enabled;
@@ -144,80 +145,27 @@ public void logRequest(Request request, Response response, Exception exception)
144145

145146
try {
146147
String userAgent = findHeader(request.getHeaders(), "user-agent");
147-
if (shouldExcludePath(request.getPath()) || shouldExcludeUserAgent(userAgent)
148-
|| (config.getCallbacks() != null && config.getCallbacks().shouldExclude(request, response))) {
148+
if (shouldExcludePath(request.getPath()) || shouldExcludeUserAgent(userAgent)) {
149149
return;
150150
}
151-
152-
// Process query params and URL
153-
if (request.getUrl() != null) {
154-
try {
155-
URL url = new URL(request.getUrl());
156-
String query = url.getQuery();
157-
if (!config.isQueryParamsIncluded()) {
158-
query = null;
159-
} else if (query != null) {
160-
query = maskQueryParams(query);
161-
}
162-
request.setUrl(new java.net.URL(url.getProtocol(), url.getHost(), url.getPort(),
163-
url.getPath() + (query != null ? "?" + query : "")).toString());
164-
} catch (MalformedURLException e) {
165-
return;
166-
}
151+
if (config.getCallbacks() != null && config.getCallbacks().shouldExclude(request, response)) {
152+
return;
167153
}
168154

169-
// Process request body
170155
if (!config.isRequestBodyIncluded() || !hasSupportedContentType(request.getHeaders())) {
171156
request.setBody(null);
172-
} else if (request.getBody() != null) {
173-
if (request.getBody().length > MAX_BODY_SIZE) {
174-
request.setBody(BODY_TOO_LARGE);
175-
} else if (config.getCallbacks() != null) {
176-
byte[] maskedBody = config.getCallbacks().maskRequestBody(request);
177-
request.setBody(maskedBody != null ? maskedBody : BODY_MASKED);
178-
if (request.getBody().length > MAX_BODY_SIZE) {
179-
request.setBody(BODY_TOO_LARGE);
180-
}
181-
}
182157
}
183-
184-
// Process response body
185158
if (!config.isResponseBodyIncluded() || !hasSupportedContentType(response.getHeaders())) {
186159
response.setBody(null);
187-
} else if (response.getBody() != null) {
188-
if (response.getBody().length > MAX_BODY_SIZE) {
189-
response.setBody(BODY_TOO_LARGE);
190-
} else if (config.getCallbacks() != null) {
191-
byte[] maskedBody = config.getCallbacks().maskResponseBody(request, response);
192-
response.setBody(maskedBody != null ? maskedBody : BODY_MASKED);
193-
if (response.getBody().length > MAX_BODY_SIZE) {
194-
response.setBody(BODY_TOO_LARGE);
195-
}
196-
}
197160
}
198161

199-
// Process headers
200-
request.setHeaders(
201-
config.isRequestHeadersIncluded()
202-
? maskHeaders(request.getHeaders()).toArray(new Header[0])
203-
: new Header[0]);
204-
response.setHeaders(
205-
config.isResponseHeadersIncluded()
206-
? maskHeaders(response.getHeaders()).toArray(new Header[0])
207-
: new Header[0]);
208-
209-
// Create log item
210-
ObjectNode item = objectMapper.createObjectNode();
211-
item.put("uuid", UUID.randomUUID().toString());
212-
item.set("request", skipEmptyValues(objectMapper.valueToTree(request)));
213-
item.set("response", skipEmptyValues(objectMapper.valueToTree(response)));
162+
ExceptionDto exceptionDto = null;
214163
if (exception != null && config.isExceptionIncluded()) {
215-
ExceptionDto exceptionDto = new ExceptionDto(exception);
216-
item.set("exception", objectMapper.valueToTree(exceptionDto));
164+
exceptionDto = new ExceptionDto(exception);
217165
}
218166

219-
String serializedItem = objectMapper.writeValueAsString(item);
220-
pendingWrites.add(serializedItem);
167+
RequestLogItem item = new RequestLogItem(request, response, exceptionDto);
168+
pendingWrites.add(item);
221169

222170
if (pendingWrites.size() > MAX_PENDING_WRITES) {
223171
pendingWrites.poll();
@@ -227,6 +175,74 @@ public void logRequest(Request request, Response response, Exception exception)
227175
}
228176
}
229177

178+
private void applyMasking(RequestLogItem item) {
179+
Request request = item.getRequest();
180+
Response response = item.getResponse();
181+
182+
if (request.getBody() != null) {
183+
// Apply user-provided masking callback for request body
184+
if (config.getCallbacks() != null) {
185+
byte[] maskedBody = config.getCallbacks().maskRequestBody(request);
186+
request.setBody(maskedBody != null ? maskedBody : BODY_MASKED);
187+
}
188+
189+
if (request.getBody().length > MAX_BODY_SIZE) {
190+
request.setBody(BODY_TOO_LARGE);
191+
}
192+
193+
// Mask request body fields (if JSON)
194+
if (!Arrays.equals(request.getBody(), BODY_TOO_LARGE) && !Arrays.equals(request.getBody(), BODY_MASKED)
195+
&& hasJsonContentType(request.getHeaders())) {
196+
request.setBody(maskJsonBody(request.getBody()));
197+
}
198+
}
199+
200+
if (response.getBody() != null) {
201+
// Apply user-provided masking callback for response body
202+
if (config.getCallbacks() != null) {
203+
byte[] maskedBody = config.getCallbacks().maskResponseBody(request, response);
204+
response.setBody(maskedBody != null ? maskedBody : BODY_MASKED);
205+
}
206+
207+
if (response.getBody().length > MAX_BODY_SIZE) {
208+
response.setBody(BODY_TOO_LARGE);
209+
}
210+
211+
// Mask response body fields (if JSON)
212+
if (!Arrays.equals(response.getBody(), BODY_TOO_LARGE) && !Arrays.equals(response.getBody(), BODY_MASKED)
213+
&& hasJsonContentType(response.getHeaders())) {
214+
response.setBody(maskJsonBody(response.getBody()));
215+
}
216+
}
217+
218+
// Process headers
219+
request.setHeaders(
220+
config.isRequestHeadersIncluded()
221+
? maskHeaders(request.getHeaders()).toArray(new Header[0])
222+
: new Header[0]);
223+
response.setHeaders(
224+
config.isResponseHeadersIncluded()
225+
? maskHeaders(response.getHeaders()).toArray(new Header[0])
226+
: new Header[0]);
227+
228+
// Process query params and URL
229+
if (request.getUrl() != null) {
230+
try {
231+
URL url = new URL(request.getUrl());
232+
String query = url.getQuery();
233+
if (!config.isQueryParamsIncluded()) {
234+
query = null;
235+
} else if (query != null) {
236+
query = maskQueryParams(query);
237+
}
238+
request.setUrl(new java.net.URL(url.getProtocol(), url.getHost(), url.getPort(),
239+
url.getPath() + (query != null ? "?" + query : "")).toString());
240+
} catch (MalformedURLException e) {
241+
// Keep original URL if malformed
242+
}
243+
}
244+
}
245+
230246
public void writeToFile() throws IOException {
231247
if (!enabled || pendingWrites.isEmpty()) {
232248
return;
@@ -236,9 +252,20 @@ public void writeToFile() throws IOException {
236252
if (currentFile == null) {
237253
currentFile = new TempGzipFile();
238254
}
239-
String item;
255+
RequestLogItem item;
240256
while ((item = pendingWrites.poll()) != null) {
241-
currentFile.writeLine(item.getBytes(StandardCharsets.UTF_8));
257+
applyMasking(item);
258+
259+
ObjectNode itemNode = objectMapper.createObjectNode();
260+
itemNode.put("uuid", item.getUuid());
261+
itemNode.set("request", skipEmptyValues(objectMapper.valueToTree(item.getRequest())));
262+
itemNode.set("response", skipEmptyValues(objectMapper.valueToTree(item.getResponse())));
263+
if (item.getException() != null) {
264+
itemNode.set("exception", objectMapper.valueToTree(item.getException()));
265+
}
266+
267+
String serializedItem = objectMapper.writeValueAsString(itemNode);
268+
currentFile.writeLine(serializedItem.getBytes(StandardCharsets.UTF_8));
242269
}
243270
} finally {
244271
lock.unlock();
@@ -389,6 +416,32 @@ private List<Header> maskHeaders(Header[] headers) {
389416
.collect(Collectors.toList());
390417
}
391418

419+
private byte[] maskJsonBody(byte[] body) {
420+
try {
421+
String json = new String(body, StandardCharsets.UTF_8);
422+
JsonNode node = objectMapper.readTree(json);
423+
maskJsonNode(node);
424+
return objectMapper.writeValueAsString(node).getBytes(StandardCharsets.UTF_8);
425+
} catch (Exception e) {
426+
return body;
427+
}
428+
}
429+
430+
private void maskJsonNode(JsonNode node) {
431+
if (node.isObject()) {
432+
ObjectNode objectNode = (ObjectNode) node;
433+
objectNode.properties().forEach(entry -> {
434+
if (entry.getValue().isTextual() && shouldMaskBodyField(entry.getKey())) {
435+
objectNode.put(entry.getKey(), MASKED);
436+
} else {
437+
maskJsonNode(entry.getValue());
438+
}
439+
});
440+
} else if (node.isArray()) {
441+
node.forEach(this::maskJsonNode);
442+
}
443+
}
444+
392445
private boolean hasSupportedContentType(Header[] headers) {
393446
String contentType = findHeader(headers, "content-type");
394447
return contentType != null && ALLOWED_CONTENT_TYPES.stream()
@@ -410,7 +463,7 @@ private String findHeader(Header[] headers, String name) {
410463

411464
private ObjectNode skipEmptyValues(ObjectNode node) {
412465
ObjectNode result = objectMapper.createObjectNode();
413-
node.fields().forEachRemaining(entry -> {
466+
node.properties().forEach(entry -> {
414467
if (entry.getValue().isNull()) {
415468
return;
416469
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.apitally.common.dto;
2+
3+
import java.util.UUID;
4+
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
public class RequestLogItem extends BaseDto {
8+
private final String uuid;
9+
private final Request request;
10+
private final Response response;
11+
private final ExceptionDto exception;
12+
13+
public RequestLogItem(Request request, Response response, ExceptionDto exception) {
14+
this.uuid = UUID.randomUUID().toString();
15+
this.request = request;
16+
this.response = response;
17+
this.exception = exception;
18+
}
19+
20+
@JsonProperty("uuid")
21+
public String getUuid() {
22+
return uuid;
23+
}
24+
25+
@JsonProperty("request")
26+
public Request getRequest() {
27+
return request;
28+
}
29+
30+
@JsonProperty("response")
31+
public Response getResponse() {
32+
return response;
33+
}
34+
35+
@JsonProperty("exception")
36+
public ExceptionDto getException() {
37+
return exception;
38+
}
39+
}

0 commit comments

Comments
 (0)