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..f8720bd8961b 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,11 +31,13 @@ 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; import org.eclipse.jetty.util.NanoTime; import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -201,7 +203,6 @@ *
The query string, prepended with a ? if a query string exists, otherwise an empty string.
* * - * *The name of the Handler or Servlet generating the response (if any).
*The HTTP response status code.
*The time at which the request was received.
@@ -262,7 +263,7 @@ *The URL path requested, not including any query string.
*The connection status when response is completed:
@@ -288,6 +289,39 @@ *The value of the VARNAME response trailer.
*The request URI.
+ *The parameter is optional and may have the be one of the following options:
+ *The value of the request attribute with the given name.
+ *(?:(?:ti)|(?:to)|(?:uri)|(?:attr)|[a-zA-Z%]))|(?[^%]+))(?.*)", Pattern.DOTALL | Pattern.MULTILINE);
private final RequestLog.Writer _requestLogWriter;
private final MethodHandle _logHandle;
@@ -444,7 +479,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 +524,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 +821,32 @@ else if ("d".equals(arg))
yield lookup.findStatic(CustomRequestLog.class, "logResponseTrailer", logTypeArg).bindTo(arg);
}
+ case "uri" ->
+ {
+ if (arg == null)
+ arg = "";
+ String method = switch (arg)
+ {
+ case "" -> "logRequestHttpUri";
+ case "-query" -> "logRequestHttpUriWithoutQuery";
+ case "-path,-query" -> "logRequestHttpUriWithoutPathQuery";
+ case "scheme" -> "logRequestScheme";
+ case "authority" -> "logRequestAuthority";
+ case "path" -> "logUrlRequestPath";
+ case "query" -> "logQueryString";
+ case "host" -> "logRequestHttpUriHost";
+ case "port" -> "logRequestHttpUriPort";
+ 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 +1198,74 @@ 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)
+ {
+ append(b, request.getHttpURI().toString());
+ }
+
+ @SuppressWarnings("unused")
+ private static void logRequestHttpUriWithoutQuery(StringBuilder b, Request request, Response response)
+ {
+ HttpURI.Mutable uri = HttpURI.build(request.getHttpURI()).query(null);
+ append(b, uri.toString());
+ }
+
+ @SuppressWarnings("unused")
+ private static void logRequestHttpUriWithoutPathQuery(StringBuilder b, Request request, Response response)
+ {
+ // HttpURI doesn't support null path so we do this manually.
+ HttpURI httpURI = request.getHttpURI();
+ if (httpURI.getScheme() != null)
+ b.append(httpURI.getScheme()).append(':');
+ if (httpURI.getHost() != null)
+ {
+ b.append("//");
+ if (httpURI.getUser() != null)
+ b.append(httpURI.getUser()).append('@');
+ b.append(httpURI.getHost());
+ }
+ int normalizedPort = URIUtil.normalizePortForScheme(httpURI.getScheme(), httpURI.getPort());
+ if (normalizedPort > 0)
+ b.append(':').append(normalizedPort);
+ }
+
+ @SuppressWarnings("unused")
+ private static void logRequestHttpUriHost(StringBuilder b, Request request, Response response)
+ {
+ append(b, request.getHttpURI().getHost());
+ }
+
+ @SuppressWarnings("unused")
+ private static void logRequestHttpUriPort(StringBuilder b, Request request, Response response)
+ {
+ b.append(request.getHttpURI().getPort());
+ }
+
+ @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..32ce7882fa02 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,207 @@ public boolean handle(Request request, Response response, Callback callback)
assertThat(log, is("42"));
}
+ @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 /path?hello=world#fragment 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() + "/path?hello=world#fragment"));
+ }
+
+ @Test
+ public void testLogRequestHttpUriWithoutQuery() throws Exception
+ {
+ start("%{-query}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 /path?hello=world#fragment 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() + "/path"));
+ }
+
+ @Test
+ public void testLogRequestHttpUriWithoutQueryAndPath() throws Exception
+ {
+ start("%{-path,-query}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 /path?hello=world#fragment 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 testLogRequestHttpUriHost() throws Exception
+ {
+ start("%{host}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 /path?hello=world#fragment 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"));
+ }
+
+ @Test
+ public void testLogRequestHttpUriPort() throws Exception
+ {
+ start("%{port}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 /path?hello=world#fragment HTTP/1.0\n\n");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ String log = _logs.poll(5, TimeUnit.SECONDS);
+ assertThat(log, is(Integer.toString(_serverConnector.getLocalPort())));
+ }
+
+ @Test
+ public void testLogRequestHttpUriScheme() throws Exception
+ {
+ start("%{scheme}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 /path?hello=world#fragment 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 testLogRequestHttpUriAuthority() throws Exception
+ {
+ start("%{authority}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 /path?hello=world#fragment 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 testLogRequestHttpUriPath() throws Exception
+ {
+ start("%{path}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 /path?hello=world#fragment HTTP/1.0\n\n");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ String log = _logs.poll(5, TimeUnit.SECONDS);
+ assertThat(log, is("/path"));
+ }
+
+ @Test
+ public void testLogRequestHttpUriQuery() throws Exception
+ {
+ start("%{query}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 /path?hello=world#fragment HTTP/1.0\n\n");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ String log = _logs.poll(5, TimeUnit.SECONDS);
+ assertThat(log, is("?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