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 support

Description

%A

No

the local IP address

%a

No

the 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.

%h

No

the remote hostname or IP address if DNS hostname lookup is not available

%I

No

the request ID. Use %{short}I format string to get the short form.

%l

No

the remote logname of the user (not supported yet, always write -)

%u

No

the name of the authenticated remote user (not supported yet, always write -)

%t

No

the 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.

%r

Yes

the request line from the client (for example, GET /path h2)

%s

No

the HTTP status code returned to the client

%b

Yes

the size of the object returned to the client, measured in bytes

%{HEADER_NAME}i

Yes

the value of the specified HTTP request header name

%{HEADER_NAME}o

Yes

the value of the specified HTTP response header name

%{ATTRIBUTE_NAME}j

Yes

the value of the specified attribute name

%{REQUEST_LOG_NAME}L

Yes

the 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

%200b

Write the size of the object returned to the client only if the response code is 200.

%200,304{User-Agent}i

Write User-Agent header value only if the response code is 200 or 304.

%!200,304{com.example.armeria.Attribute#KEY}j

Write 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

method

the HTTP method value of the request

path

the absolute path part of the HTTP request URI

query

the query part of the HTTP request URI

requestStartTimeMillis

the time when the processing of the request started, in milliseconds since the epoch

requestDurationMillis

the duration that was taken to consume or produce the request completely, in milliseconds

requestDurationNanos

the duration that was taken to consume or produce the request completely, in nanoseconds

requestLength

the length of the request content

requestCause

the cause of request processing failure. The class name of the cause and the detail message of it will be contained if exists.

requestContentPreview

the preview of the request content

responseStartTimeMillis

the time when the processing of the response started, in milliseconds since the epoch

responseDurationMillis

the duration that was taken to consume or produce the response completely, in milliseconds

responseDurationNanos

the duration that was taken to consume or produce the response completely, in nanoseconds

responseLength

the length of the response content

responseCause

the cause of response processing failure. The class name of the cause and the detail message of it will be contained if exists.

responseContentPreview

the preview of the response content

totalDurationMillis

the amount of time taken since the request processing started and until the response processing ended, in milliseconds

totalDurationNanos

the amount of time taken since the request processing started and until the response processing ended, in nanoseconds

sessionProtocol

the session protocol of the request. e.g. h1, h2, h1c or h2c

serializationFormat

the serialization format of the request. e.g. tbinary, ttext, tcompact, tjson or none

scheme

the scheme value printed as serializationFormat+sessionProtocol

host

the host name of the request

status

the status code and its reason phrase of the response. e.g. 200 OK

statusCode

the 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.

Formatter

Description

Example

BASIC_ISO_DATE

Basic ISO date

20111203

ISO_LOCAL_DATE

ISO Local Date

2011-12-03

ISO_OFFSET_DATE

ISO Date with offset

2011-12-03+01:00

ISO_DATE

ISO Date with or without offset

2011-12-03+01:00; 2011-12-03

ISO_LOCAL_TIME

Time without offset

10:15:30

ISO_OFFSET_TIME

Time with offset

10:15:30+01:00

ISO_TIME

Time with or without offset

10:15:30+01:00; 10:15:30

ISO_LOCAL_DATE_TIME

ISO Local Date and Time

2011-12-03T10:15:30

ISO_OFFSET_DATE_TIME

Date Time with Offset

2011-12-03T10:15:30+01:00

ISO_ZONED_DATE_TIME

Zoned Date Time

2011-12-03T10:15:30+01:00[Europe/Paris]

ISO_DATE_TIME

Date and time with ZoneId

2011-12-03T10:15:30+01:00[Europe/Paris]

ISO_ORDINAL_DATE

Year and day of year

2012-337

ISO_WEEK_DATE

Year and Week

2012-W48-6

ISO_INSTANT

Date and Time of an Instant

2011-12-03T10:15:30Z

RFC_1123_DATE_TIME

RFC 1123 / RFC 822

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