Skip to main content

Writing an access log

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 supportDescription
%ANothe local IP address
%aNothe IP address of the client who initiated a request. Use %{c}a format string to get the remote IP address where the channel is connected to, which may yield a different value when there is an intermediary proxy server. This value relies on the clientAddress() and remoteAddress() methods of ServiceRequestContext, please refer to Getting an IP address of a client who initiated a request about how to configure the ServerBuilder to get the client address you interest.
%hNothe remote hostname or IP address if DNS hostname lookup is not available
%INothe request ID. Use %{short}I format string to get the short form.
%lNothe remote logname of the user (not supported yet, always write -)
%uNothe name of the authenticated remote user (not supported yet, always write -)
%tNothe date, time and time zone that the request was received, by default in strftime format %d/%b/%Y:%H:%M:%S %z. (for example, 10/Oct/2000:13:55:36 -0700) Refer to Customizing timestamp format for more information.
%rYesthe request line from the client (for example, GET /path h2)
%sNothe HTTP status code returned to the client
%bYesthe size of the object returned to the client, measured in bytes
%{HEADER_NAME}iYesthe value of the specified HTTP request header name
%{HEADER_NAME}oYesthe value of the specified HTTP response header name
%{ATTRIBUTE_NAME}jYesthe value of the specified attribute name
%{REQUEST_LOG_NAME}LYesthe value of the specified variable of the RequestLog. Refer to Retrieving values from RequestLog for more information.

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
%200bWrite the size of the object returned to the client only if the response code is 200.
%200,304{User-Agent}iWrite User-Agent header value only if the response code is 200 or 304.
%!200,304{com.example.armeria.Attribute#KEY}jWrite the value of the specified attribute only if the response code is neither 200 nor 304.

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
methodthe HTTP method value of the request
paththe absolute path part of the HTTP request URI
querythe query part of the HTTP request URI
requestStartTimeMillisthe time when the processing of the request started, in milliseconds since the epoch
requestDurationMillisthe duration that was taken to consume or produce the request completely, in milliseconds
requestDurationNanosthe duration that was taken to consume or produce the request completely, in nanoseconds
requestLengththe length of the request content
requestCausethe cause of request processing failure. The class name of the cause and the detail message of it will be contained if exists.
requestContentPreviewthe preview of the request content
responseStartTimeMillisthe time when the processing of the response started, in milliseconds since the epoch
responseDurationMillisthe duration that was taken to consume or produce the response completely, in milliseconds
responseDurationNanosthe duration that was taken to consume or produce the response completely, in nanoseconds
responseLengththe length of the response content
responseCausethe cause of response processing failure. The class name of the cause and the detail message of it will be contained if exists.
responseContentPreviewthe preview of the response content
totalDurationMillisthe amount of time taken since the request processing started and until the response processing ended, in milliseconds
totalDurationNanosthe amount of time taken since the request processing started and until the response processing ended, in nanoseconds
sessionProtocolthe session protocol of the request. e.g. h1, h2, h1c or h2c
serializationFormatthe serialization format of the request. e.g. tbinary, ttext, tcompact, tjson or none
schemethe scheme value printed as serializationFormat+sessionProtocol
hostthe host name of the request
statusthe status code and its reason phrase of the response. e.g. 200 OK
statusCodethe status code of the response. e.g. 200

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.

FormatterDescriptionExample
BASIC_ISO_DATEBasic ISO date20111203
ISO_LOCAL_DATEISO Local Date2011-12-03
ISO_OFFSET_DATEISO Date with offset2011-12-03+01:00
ISO_DATEISO Date with or without offset2011-12-03+01:00; 2011-12-03
ISO_LOCAL_TIMETime without offset10:15:30
ISO_OFFSET_TIMETime with offset10:15:30+01:00
ISO_TIMETime with or without offset10:15:30+01:00; 10:15:30
ISO_LOCAL_DATE_TIMEISO Local Date and Time2011-12-03T10:15:30
ISO_OFFSET_DATE_TIMEDate Time with Offset2011-12-03T10:15:30+01:00
ISO_ZONED_DATE_TIMEZoned Date Time2011-12-03T10:15:30+01:00[Europe/Paris]
ISO_DATE_TIMEDate and time with ZoneId2011-12-03T10:15:30+01:00[Europe/Paris]
ISO_ORDINAL_DATEYear and day of year2012-337
ISO_WEEK_DATEYear and Week2012-W48-6
ISO_INSTANTDate and Time of an Instant2011-12-03T10:15:30Z
RFC_1123_DATE_TIMERFC 1123 / RFC 822Tue, 3 Jun 2008 11:05:30 GMT

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());
})
...

Like Armeria?
Star us ⭐️

×