Writing an access log
Table of contents
Configuring logging framework
To write an access log, you need to configure a logging framework first. The following configurations are
simple examples of logback.xml
and log4j2.xml
respectively.
logback
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>access.%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!-- each file should be at most 1GB, keep 30 days worth of history, but at most 30GB -->
<maxFileSize>1GB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>30GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<logger name="com.linecorp.armeria.logging.access" level="INFO" additivity="false">
<appender-ref ref="ACCESS"/>
</logger>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
log4j2
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<RollingFile name="ACCESS" fileName="access.log"
filePattern="access.%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout>
<Pattern>%m%n</Pattern>
</PatternLayout>
<Policies>
<!-- daily rollover -->
<TimeBasedTriggeringPolicy/>
<!-- each file should be at most 1GB -->
<SizeBasedTriggeringPolicy size="1GB"/>
</Policies>
<!-- keep 30 archives -->
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.linecorp.armeria.logging.access" level="INFO" additivity="false">
<AppenderRef ref="ACCESS"/>
</Logger>
<Root level="DEBUG">
<AppenderRef ref="CONSOLE"/>
</Root>
</Loggers>
</Configuration>
Customizing a log format
Access logging is disabled by default. If you want to enable it, you need to specify an AccessLogWriter
.
You may use one of the pre-defined log formats.
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.logging.AccessLogWriter;
ServerBuilder sb = Server.builder();
// Use NCSA common log format.
sb.accessLogWriter(AccessLogWriter.common(), true);
// Use NCSA combined log format.
sb.accessLogWriter(AccessLogWriter.combined(), true);
// Use your own log format.
sb.accessLogFormat("...log format...");
...
You can have different AccessLogWriters
for VirtualHosts
and Services
.
ServerBuilder sb = Server.builder();
AccessLogWriter fallbackLogWriter = ...
sb.accessLogWriter(fallbackLogWriter);
sb.virtualHost("foo.com")
.accessLogWriter(AccessLogWriter.combined(), true)
.service((ctx, req) -> HttpResponse.of(OK)); // This service will log using the combined format.
...
sb.route()
.get("/commonAccessLog")
.accessLogWriter(AccessLogWriter.common(), true)
.build((ctx, req) -> HttpResponse.of(OK)); // This service will log using the common format.
// This service will log using the fallbackLogWriter.
sb.service("/fallbackLogWriter", (ctx, req) -> HttpResponse.of(OK));
Pre-defined log formats are as follows.
Tokens for the log format are listed in the following table.
Tokens | Condition support | Description |
---|---|---|
| No | the local IP address |
| No | the IP address of the client who initiated a
request. Use |
| No | the remote hostname or IP address if DNS hostname lookup is not available |
| No | the request ID. Use |
| No | the remote logname of the user
(not supported yet, always write |
| No | the name of the authenticated remote user
(not supported yet, always write |
| No | the date, time and time zone that the request
was received, by default in |
| Yes | the request line from the client
(for example, |
| No | the HTTP status code returned to the client |
| Yes | the size of the object returned to the client, measured in bytes |
| Yes | the value of the specified HTTP request header name |
| Yes | the value of the specified HTTP response header name |
| Yes | the value of the specified attribute name |
| Yes | the value of the specified variable of the
|
Some tokens can have a condition of the response status code and the log message can be omitted with the condition.
Example of a condition | Description |
---|---|
| Write the size of the object returned to the
client only if the response code is |
| Write |
| Write the value of the specified attribute
only if the response code is neither |
Retrieving values from RequestLog
RequestLog
holds information about the request, so a user may want to write these values to his or
her access log file. To write them in a simple way, %{variable}L
token is provided with the following
supported variable:
Name | Description |
---|---|
| the HTTP method value of the request |
| the absolute path part of the HTTP request URI |
| the query part of the HTTP request URI |
| the time when the processing of the request started, in milliseconds since the epoch |
| the duration that was taken to consume or produce the request completely, in milliseconds |
| the duration that was taken to consume or produce the request completely, in nanoseconds |
| the length of the request content |
| the cause of request processing failure. The class name of the cause and the detail message of it will be contained if exists. |
| the preview of the request content |
| the time when the processing of the response started, in milliseconds since the epoch |
| the duration that was taken to consume or produce the response completely, in milliseconds |
| the duration that was taken to consume or produce the response completely, in nanoseconds |
| the length of the response content |
| the cause of response processing failure. The class name of the cause and the detail message of it will be contained if exists. |
| the preview of the response content |
| the amount of time taken since the request processing started and until the response processing ended, in milliseconds |
| the amount of time taken since the request processing started and until the response processing ended, in nanoseconds |
| the session protocol of the request.
e.g. |
| the serialization format of the request.
e.g. |
| the scheme value printed as |
| the host name of the request |
| the status code and its reason phrase of the response.
e.g. |
| the status code of the response. e.g. |
Customizing timestamp format
You can specify a new date/time format for the %t
token with
DateTimeFormatter.
You can use one of the following formatters which is provided by JDK as a variable of the %t
token,
e.g. %{BASIC_ISO_DATE}t
. If you want to use your own pattern, you can specify it as the variable,
e.g. %{yyyy MM dd}t
.
Formatter | Description | Example |
---|---|---|
| Basic ISO date |
|
| ISO Local Date |
|
| ISO Date with offset |
|
| ISO Date with or without offset |
|
| Time without offset |
|
| Time with offset |
|
| Time with or without offset |
|
| ISO Local Date and Time |
|
| Date Time with Offset |
|
| Zoned Date Time |
|
| Date and time with ZoneId |
|
| Year and day of year |
|
| Year and Week |
|
| Date and Time of an Instant |
|
| RFC 1123 / RFC 822 |
|
Customizing an access log writer
You can specify your own log writer which implements a Consumer
of RequestLog
.
ServerBuilder sb = Server.builder();
sb.accessLogWriter(requestLog -> {
// Write your access log with the given RequestLog instance.
...
}, true);
Customizing an access logger
Armeria uses an SLF4J logger whose name is based on a reversed domain name of each virtual host by default, e.g.
com.linecorp.armeria.logging.access.com.example
for*.example.com
com.linecorp.armeria.logging.access.com.linecorp
for*.linecorp.com
Alternatively, you can specify your own mapper or logger for a VirtualHost
, e.g.
ServerBuilder sb = Server.builder();
// Using the specific logger name.
sb.accessLogger("com.example.my.access.logs");
...
// Using your own logger.
Logger logger = LoggerFactory.getLogger("com.example2.my.access.logs");
sb.accessLogger(Logger);
...
// Using the mapper which sets an access logger with the given VirtualHost instance.
sb.accessLogger(virtualHost -> {
// Return the logger.
// Do not return null. Otherwise, it will raise an IllegalStateException.
return LoggerFactory.getLogger("com.example.my.access.logs." + virtualHost.defaultHostname());
});
...
You can also specify your own logger for the specific VirtualHost
. In this case, the mapper or logger
you set for a specific VirtualHost
will override the access logger set via
ServerBuilder.accessLogger()
.
// Using the specific logger name.
sb.virtualHost("*.example.com")
.accessLogger("com.example.my.access.logs")
.and()
...
// Using your own logger.
Logger logger = LoggerFactory.getLogger("com.example2.my.access.logs");
sb.virtualHost("*.example2.com")
.accessLogger(Logger)
.and()
...
// Using the mapper which sets an access logger with the given VirtualHost instance.
sb.virtualHost("*.example3.com")
.accessLogger(virtualHost -> {
// Return the logger.
// Do not return null. Otherwise, it will raise an IllegalStateException.
return LoggerFactory.getLogger("com.example.my.access.logs." + virtualHost.defaultHostname());
})
...