From c8c891b899bef7b385bde8b43b75cbc67c0d9cd5 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Fri, 27 Sep 2024 16:55:18 +1000 Subject: [PATCH] Issue #9980 - add additional codes for CustomRequestLog Signed-off-by: Lachlan Roberts --- .../jetty/server/CustomRequestLog.java | 103 ++++++++++++++++-- .../jetty/server/CustomRequestLogTest.java | 101 +++++++++++++++++ 2 files changed, 197 insertions(+), 7 deletions(-) diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java index 38f61386706a..58ccdcc5040f 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.QuotedCSV; import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.util.DateCache; @@ -201,7 +202,6 @@ *

The query string, prepended with a ? if a query string exists, otherwise an empty string.

* * - * * * %r * @@ -214,13 +214,13 @@ *

The name of the Handler or Servlet generating the response (if any).

* * - * + * * %s * *

The HTTP response status code.

* * - * + * * %{format|timeZone|locale}t * *

The time at which the request was received.

@@ -262,7 +262,7 @@ *

The URL path requested, not including any query string.

* * - * + * * %X * *

The connection status when response is completed:

@@ -288,6 +288,36 @@ *

The value of the VARNAME response trailer.

* * + * + * %A + * + *

The Request authority (which might differ from the Host header).

+ * + * + * + * %h + * + *

The Request scheme.

+ * + * + * + * %uri + * + *

The entire Request HttpURI (excluding query).

+ * + * + * + * %{q}uri + * + *

The entire Request HttpURI (including query).

+ * + * + * + * %{attributeName}attr + * + *

A request attribute.

+ * + * * * */ @@ -309,6 +339,7 @@ public record LogDetail(String handlerName, String realPath) public static final String LOG_DETAIL = CustomRequestLog.class.getName() + ".logDetail"; private static final Logger LOG = LoggerFactory.getLogger(CustomRequestLog.class); private static final ThreadLocal _buffers = ThreadLocal.withInitial(() -> new StringBuilder(256)); + private static final Pattern PATTERN = Pattern.compile("^(?:%(?!?[0-9,]+)?(?:\\{(?[^}]+)})?(?(?:(?:ti)|(?:to)|(?:uri)|(?:attr)|[a-zA-Z%]))|(?[^%]+))(?.*)", Pattern.DOTALL | Pattern.MULTILINE); private final RequestLog.Writer _requestLogWriter; private final MethodHandle _logHandle; @@ -444,7 +475,7 @@ protected void doStart() throws Exception private static void append(StringBuilder buf, String s) { - if (s == null || s.length() == 0) + if (s == null || s.isEmpty()) buf.append('-'); else buf.append(s); @@ -489,8 +520,6 @@ private static List getTokens(String formatString) {PARAM} is an optional string parameter to the percent code. CODE is a 1 to 2 character string corresponding to a format code. */ - final Pattern PATTERN = Pattern.compile("^(?:%(?!?[0-9,]+)?(?:\\{(?[^}]+)})?(?(?:(?:ti)|(?:to)|[a-zA-Z%]))|(?[^%]+))(?.*)", Pattern.DOTALL | Pattern.MULTILINE); - List tokens = new ArrayList<>(); String remaining = formatString; while (remaining.length() > 0) @@ -788,6 +817,27 @@ else if ("d".equals(arg)) yield lookup.findStatic(CustomRequestLog.class, "logResponseTrailer", logTypeArg).bindTo(arg); } + case "A" -> lookup.findStatic(CustomRequestLog.class, "logRequestAuthority", logType); + case "h" -> lookup.findStatic(CustomRequestLog.class, "logRequestScheme", logType); + case "uri" -> + { + if (arg == null) + arg = ""; + String method = switch (arg) + { + case "" -> "logRequestHttpUri"; + case "q" -> "logRequestHttpUriQuery"; + default -> throw new IllegalArgumentException("Invalid arg for %uri"); + }; + + yield lookup.findStatic(CustomRequestLog.class, method, logType); + } + case "attr" -> + { + MethodType logRequestAttribute = methodType(void.class, String.class, StringBuilder.class, Request.class, Response.class); + yield lookup.findStatic(CustomRequestLog.class, "logRequestAttribute", logRequestAttribute).bindTo(arg); + } + default -> throw new IllegalArgumentException("Unsupported code %" + code); }; @@ -1139,4 +1189,43 @@ private static void logResponseTrailer(String arg, StringBuilder b, Request requ else b.append('-'); } + + @SuppressWarnings("unused") + private static void logRequestAuthority(StringBuilder b, Request request, Response response) + { + HttpURI httpURI = request.getHttpURI(); + if (httpURI.hasAuthority()) + append(b, httpURI.getAuthority()); + else + b.append('-'); + } + + @SuppressWarnings("unused") + private static void logRequestScheme(StringBuilder b, Request request, Response response) + { + append(b, request.getHttpURI().getScheme()); + } + + @SuppressWarnings("unused") + private static void logRequestHttpUri(StringBuilder b, Request request, Response response) + { + HttpURI.Mutable uri = HttpURI.build(request.getHttpURI()).query(null); + append(b, uri.toString()); + } + + @SuppressWarnings("unused") + private static void logRequestHttpUriQuery(StringBuilder b, Request request, Response response) + { + append(b, request.getHttpURI().toString()); + } + + @SuppressWarnings("unused") + private static void logRequestAttribute(String arg, StringBuilder b, Request request, Response response) + { + Object attribute = request.getAttribute(arg); + if (attribute != null) + append(b, attribute.toString()); + else + b.append('-'); + } } diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java index 9921bba88c32..1ef1cc6d5bfb 100644 --- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CustomRequestLogTest.java @@ -818,6 +818,107 @@ public boolean handle(Request request, Response response, Callback callback) assertThat(log, is("42")); } + @Test + public void testLogRequestAuthority() throws Exception + { + start("%A", new SimpleHandler() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + Content.Sink.write(response, false, "hello", Callback.NOOP); + callback.succeeded(); + return true; + } + }); + + HttpTester.Response response = getResponse("GET / HTTP/1.0\n\n"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String log = _logs.poll(5, TimeUnit.SECONDS); + assertThat(log, is("127.0.0.1:" + _serverConnector.getLocalPort())); + } + + @Test + public void testLogRequestScheme() throws Exception + { + start("%h", new SimpleHandler() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + Content.Sink.write(response, false, "hello", Callback.NOOP); + callback.succeeded(); + return true; + } + }); + + HttpTester.Response response = getResponse("GET / HTTP/1.0\n\n"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String log = _logs.poll(5, TimeUnit.SECONDS); + assertThat(log, is("http")); + } + + @Test + public void testLogRequestHttpUri() throws Exception + { + start("%uri", new SimpleHandler() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + Content.Sink.write(response, false, "hello", Callback.NOOP); + callback.succeeded(); + return true; + } + }); + + HttpTester.Response response = getResponse("GET /?hello=world HTTP/1.0\n\n"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String log = _logs.poll(5, TimeUnit.SECONDS); + assertThat(log, is("http://127.0.0.1:" + _serverConnector.getLocalPort() + "/")); + } + + @Test + public void testLogRequestHttpUriWithQuery() throws Exception + { + start("%{q}uri", new SimpleHandler() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + Content.Sink.write(response, false, "hello", Callback.NOOP); + callback.succeeded(); + return true; + } + }); + + HttpTester.Response response = getResponse("GET /?hello=world HTTP/1.0\n\n"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String log = _logs.poll(5, TimeUnit.SECONDS); + assertThat(log, is("http://127.0.0.1:" + _serverConnector.getLocalPort() + "/?hello=world")); + } + + @Test + public void testLogRequestAttribute() throws Exception + { + start("%{myAttribute}attr", new SimpleHandler() + { + @Override + public boolean handle(Request request, Response response, Callback callback) + { + request.setAttribute("myAttribute", "value1234"); + Content.Sink.write(response, false, "hello", Callback.NOOP); + callback.succeeded(); + return true; + } + }); + + HttpTester.Response response = getResponse("GET /?hello=world HTTP/1.0\n\n"); + assertEquals(HttpStatus.OK_200, response.getStatus()); + String log = _logs.poll(5, TimeUnit.SECONDS); + assertThat(log, is("value1234")); + } + class TestRequestLogWriter implements RequestLog.Writer { @Override