Skip to content

Commit

Permalink
Support for access log in Vert.x HTTP
Browse files Browse the repository at this point in the history
Fixes #5556
  • Loading branch information
stuartwdouglas committed Apr 6, 2020
1 parent 3df1f24 commit 7b9c133
Show file tree
Hide file tree
Showing 53 changed files with 3,202 additions and 138 deletions.
58 changes: 35 additions & 23 deletions docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -218,31 +218,43 @@ link:http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#predicates-

=== Configuring HTTP Access Logs

You can add HTTP request logging by configuring the `AccessHandler` in the `undertow-handlers.conf` file.
You can add HTTP request logging by configuring it in `application.properties`. There are two options for logging,
either logging to the standard JBoss logging output, or logging to a dedicated file.

The simplest possible configuration can be a standard Apache `common` Log Format:
include: quarkus-vertx-http-config-group-access-log-config.adoc

[source]
----
access-log('common')
----

This will log every request using the standard Quarkus logging infrastructure under the `io.undertow.accesslog` category.

You can customize the category like this:


[source]
----
access-log(format='common', category='my.own.category')
----

Finally the logging format can be customized:

[source]
----
access-log(format='%h %l %u %t "%r" %s %b %D "%{i,Referer}" "%{i,User-Agent}" "%{i,X-Request-ID}"', category='my.own.category')
----
[frame="topbot",options="header"]
|===
|Attribute |Short Form|Long Form
|Remote IP address | `%a` | `%{REMOTE_IP}`
|Local IP address | `%A` | `%{LOCAL_IP}`
|Bytes sent, excluding HTTP headers, or '-' if no bytes were sent | `%b` |
|Bytes sent, excluding HTTP headers | `%B` | `%{BYTES_SENT}`
|Remote host name | `%h` | `%{REMOTE_HOST}`
|Request protocol | `%H` | `%{PROTOCOL}`
|Request method | `%m` | `%{METHOD}`
|Local port | `%p` | `%{LOCAL_PORT}`
|Query string (prepended with a '?' if it exists, otherwise an empty string) | `%q` | `%{QUERY_STRING}`
|First line of the request | `%r` | `%{REQUEST_LINE}`
|HTTP status code of the response | `%s` | `%{RESPONSE_CODE}`
|Date and time, in Common Log Format format | `%t` | `%{DATE_TIME}`
|Remote user that was authenticated | `%u` | `%{REMOTE_USER}`
|Requested URL path | `%U` | `%{REQUEST_URL}`
|Request relative path | `%R` | `%{RELATIVE_PATH}`
|Local server name | `%v` | `%{LOCAL_SERVER_NAME}`
|Time taken to process the request, in millis | `%D` | `%{RESPONSE_TIME}`
|Time taken to process the request, in seconds | `%T` |
|Time taken to process the request, in micros | | `%{RESPONSE_TIME_MICROS}`
|Time taken to process the request, in nanos | | `%{RESPONSE_TIME_NANOS}`
|Current request thread name | `%I` | `%{THREAD_NAME}`
|SSL cypher | | `%{SSL_CIPHER}`
|SSL client certificate | | `%{SSL_CLIENT_CERT}`
|SSL session id | | `%{SSL_SESSION_ID}`
|Cookie value | | `%{c,cookie_name}`
|Query parameter | | `%{q,query_param_name}`
|Request header | | `%{i,request_header_name}`
|Response header | | `%{o,response_header_name}`
|===

=== web.xml

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationStartBuildItem;
import io.quarkus.deployment.builditem.ExecutorBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
Expand Down Expand Up @@ -119,8 +120,8 @@ ServiceStartBuildItem finalizeRouter(
BodyHandlerBuildItem bodyHandlerBuildItem,
BuildProducer<ShutdownListenerBuildItem> shutdownListenerBuildItemBuildProducer,
ShutdownConfig shutdownConfig,
CoreVertxBuildItem core // Injected to be sure that Vert.x has been produced before calling this method.
)
CoreVertxBuildItem core, // Injected to be sure that Vert.x has been produced before calling this method.
ExecutorBuildItem executorBuildItem)
throws BuildException, IOException {
Optional<DefaultRouteBuildItem> defaultRoute;
if (defaultRoutes == null || defaultRoutes.isEmpty()) {
Expand Down Expand Up @@ -149,7 +150,7 @@ ServiceStartBuildItem finalizeRouter(
defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null),
listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode(),
!requireBodyHandlerBuildItems.isEmpty(), bodyHandler, httpConfiguration, gracefulShutdownFilter,
shutdownConfig);
shutdownConfig, executorBuildItem.getExecutorProxy());

return new ServiceStartBuildItem("vertx-http");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2018 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.quarkus.vertx.http.accesslog;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.awaitility.Awaitility;
import org.awaitility.core.ThrowingRunnable;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.ByteArrayAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.bootstrap.util.IoUtils;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

/**
* Tests writing the access log to a file
*
* @author Stuart Douglas
*/
public class AccessLogFileTestCase {

@RegisterExtension
public static QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(new Supplier<JavaArchive>() {
@Override
public JavaArchive get() {
Path logDirectory;
try {
logDirectory = Files.createTempDirectory("quarkus-tests");
//backslash is an escape char, we need this to be properly formatted for windows
Properties p = new Properties();
p.setProperty("quarkus.http.access-log.enabled", "true");
p.setProperty("quarkus.http.access-log.log-to-file", "true");
p.setProperty("quarkus.http.access-log.base-file-name", "server");
p.setProperty("quarkus.http.access-log.log-directory", logDirectory.toAbsolutePath().toString());
ByteArrayOutputStream out = new ByteArrayOutputStream();
p.store(out, null);

return ShrinkWrap.create(JavaArchive.class)
.add(new ByteArrayAsset(out.toByteArray()),
"application.properties");

} catch (IOException e) {
throw new RuntimeException(e);
}
}
});

@ConfigProperty(name = "quarkus.http.access-log.log-directory")
Path logDirectory;

@BeforeEach
public void before() throws IOException {
Files.createDirectories(logDirectory);
}

@AfterEach
public void after() throws IOException {
IoUtils.recursiveDelete(logDirectory);
}

@Test
public void testSingleLogMessageToFile() throws IOException, InterruptedException {
RestAssured.get("/does-not-exist");

Awaitility.given().pollInterval(100, TimeUnit.MILLISECONDS)
.atMost(10, TimeUnit.SECONDS)
.untilAsserted(new ThrowingRunnable() {
@Override
public void run() throws Throwable {
try (Stream<Path> files = Files.list(logDirectory)) {
Assertions.assertEquals(1, (int) files.count());
}
Path path = logDirectory.resolve("server.log");
Assertions.assertTrue(Files.exists(path));
String data = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
Assertions.assertTrue(data.contains("404"));
Assertions.assertTrue(data.contains("/does-not-exist"));
}
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.quarkus.vertx.http.runtime;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class AccessLogConfig {

/**
* If access logging is enabled. By default this will log via the standard logging facility
*/
@ConfigItem(defaultValue = "false")
public boolean enabled;

/**
* The access log pattern:
*
* If this is the string 'common' or 'combined' then this will use one of the specified named formats:
*
* common: %h %l %u %t "%r" %s %b
* combined: %h %l %u %t "%r" %s %b "%{i,Referer}" "%{i,User-Agent}"
*
* Otherwise consult the Quarkus documentation for the full list of variables that can be used.
*
*/
@ConfigItem(defaultValue = "common")
public String pattern;

/**
* If logging should be done to a separate file.
*/
@ConfigItem(defaultValue = "false")
public boolean logToFile;

/**
* The access log file base name, defaults to 'quarkus' which will give a log file
* name of 'quarkus.log'.
*
*/
@ConfigItem(defaultValue = "quarkus")
public String baseFileName;

/**
* The log directory to use when logging access to a file
*
* If this is not set then the current working directory is used.
*/
@ConfigItem
public Optional<String> logDirectory;

/**
* The log file suffix
*/
@ConfigItem(defaultValue = ".log")
public String logSuffix;

/**
* The log category to use if logging is being done via the standard log mechanism (i.e. if base-file-name is empty).
*
*/
@ConfigItem(defaultValue = "io.quarkus.http.access-log")
public String category;

/**
* If the log should be rotated daily
*/
@ConfigItem(defaultValue = "true")
public boolean rotate;

}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ public class HttpConfiguration {
@ConfigItem(defaultValue = "false")
public boolean domainSocketEnabled;

/**
* If this is true then the request start time will be recorded to enable logging of total request time.
*
* This has a small performance penalty, so is disabled by default.
*/
@ConfigItem
public boolean recordRequestStartTime;

AccessLogConfig accessLog;

public int determinePort(LaunchMode launchMode) {
return launchMode == LaunchMode.TEST ? testPort : port;
}
Expand All @@ -188,7 +198,7 @@ public int determineSslPort(LaunchMode launchMode) {
return launchMode == LaunchMode.TEST ? testSslPort : sslPort;
}

public static enum InsecureRequests {
public enum InsecureRequests {
ENABLED,
REDIRECT,
DISABLED;
Expand Down
Loading

0 comments on commit 7b9c133

Please sign in to comment.