diff --git a/src/main/java/com/uid2/shared/audit/Audit.java b/src/main/java/com/uid2/shared/audit/Audit.java index 95ec2dc8..77bdc69e 100644 --- a/src/main/java/com/uid2/shared/audit/Audit.java +++ b/src/main/java/com/uid2/shared/audit/Audit.java @@ -1,5 +1,6 @@ package com.uid2.shared.audit; +import com.uid2.shared.util.LogFmtLineBuilder; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.Json; @@ -72,6 +73,29 @@ public String toString() { return toJson().encode(); } + public String toLogFmtLine() { + LogFmtLineBuilder builder = new LogFmtLineBuilder(); + + builder.with("timestamp", timestamp.toString()); + builder.with("log_type", logType); + builder.with("source", source); + builder.with("status", status); + builder.with("method", method); + builder.with("endpoint", endpoint); + builder.with("trace_id", traceId); + builder.with("actor", actor); + if (uidTraceId != null) { + builder.with("uid_trace_id", uidTraceId); + } + if (uidInstanceId != null) { + builder.with("uid_instance_id", uidInstanceId); + } + if (queryParams != null) builder.with("query_params", queryParams); + if (requestBody != null) builder.with("request_body", requestBody); + + return builder.build(); + } + public static class Builder { private final int status; private final String method; @@ -288,7 +312,7 @@ public void log(RoutingContext ctx, AuditParams params) { } AuditRecord auditRecord = builder.build(); - LOGGER.info(auditRecord.toString()); + LOGGER.info(auditRecord.toLogFmtLine()); } catch (Exception e) { LOGGER.warn("Failed to log audit record", e); } diff --git a/src/main/java/com/uid2/shared/util/LogFmtLineBuilder.java b/src/main/java/com/uid2/shared/util/LogFmtLineBuilder.java new file mode 100644 index 00000000..94c3064c --- /dev/null +++ b/src/main/java/com/uid2/shared/util/LogFmtLineBuilder.java @@ -0,0 +1,63 @@ +package com.uid2.shared.util; + +import io.vertx.core.json.JsonObject; + +import java.util.Map; + +public class LogFmtLineBuilder { + private final StringBuilder stringBuilder; + + public LogFmtLineBuilder() { + this.stringBuilder = new StringBuilder(); + } + + private String escape(String value) { + if (value == null) { + return "null"; + } + + if (value.contains(" ") || value.contains("\"") || value.contains("=") || value.contains("\n") || + value.contains("\r") || value.contains("\t") || value.contains("\\")) { + return "\"" + value.replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t") + "\""; + } + + return value; + } + + public LogFmtLineBuilder with(String key, String value) { + if (!stringBuilder.isEmpty()) { + stringBuilder.append(" "); + } + stringBuilder.append(key).append("=").append(escape(value)); + return this; + } + + public LogFmtLineBuilder with(Map map) { + if (map != null) { + for (Map.Entry entry : map.entrySet()) { + with(entry.getKey(), entry.getValue()); + } + } + return this; + } + + public LogFmtLineBuilder with(String key, int value) { + return with(key, String.valueOf(value)); + } + + // Only supports one level of nesting + public LogFmtLineBuilder with(String key, JsonObject obj) { + for (String objKey : obj.fieldNames()) { + with(key+ "." + objKey, obj.getString(objKey)); + } + return this; + } + + public String build() { + return stringBuilder.toString(); + } +} diff --git a/src/test/java/com/uid2/shared/audit/AuditTest.java b/src/test/java/com/uid2/shared/audit/AuditTest.java index 22e26869..2bed88e3 100644 --- a/src/test/java/com/uid2/shared/audit/AuditTest.java +++ b/src/test/java/com/uid2/shared/audit/AuditTest.java @@ -66,18 +66,13 @@ public void testAudit() throws JsonProcessingException { Optional maybeEvent = listAppender.list.stream() .filter(event -> event.getFormattedMessage().contains("GET")) .findFirst(); - String jsonLog = maybeEvent.get().getFormattedMessage(); - ObjectMapper mapper = new ObjectMapper(); - JsonNode jsonNode = mapper.readTree(jsonLog); - - assertThat(jsonNode.get("method").asText()).isEqualTo("GET"); - assertThat(jsonNode.get("status").asInt()).isEqualTo(200); - assertThat(jsonNode.get("source").asText()).isEqualTo("admin"); - - JsonNode actor = jsonNode.get("actor"); - assertThat(actor).isNotNull(); - assertThat(actor.get("user_agent").asText()).isEqualTo("JUnit-Test-Agent"); - assertThat(actor.get("ip").asText()).isEqualTo("127.0.0.1"); + String auditLog = maybeEvent.get().getFormattedMessage(); + + assertThat(auditLog.contains("method=GET")).isTrue(); + assertThat(auditLog.contains("status=200")).isTrue(); + assertThat(auditLog.contains("source=admin")).isTrue(); + assertThat(auditLog.contains("actor.user_agent=JUnit-Test-Agent")).isTrue(); + assertThat(auditLog.contains("actor.ip=127.0.0.1")).isTrue(); }