Logging contextual information

With Armeria's Logback integration, you can log the properties of the RequestContext of the request being handled. RequestContextExportingAppender is a Logback appender that exports the properties of the current RequestContext to MDC (mapped diagnostic context).

First, you need the armeria-logback dependency:

build.gradle
dependencies {
    implementation platform('com.linecorp.armeria:armeria-bom:1.31.3')

    ...
    implementation 'com.linecorp.armeria:armeria-logback'
}

Then, let's look at the following example:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} %X{remote.ip} %X{tls.cipher}
               %X{req.headers.user-agent} %X{attrs.some_value} %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">
    <appender-ref ref="CONSOLE" />
    <export>remote.ip</export>
    <export>tls.cipher</export>
    <export>req.headers.user-agent</export>
    <export>attrs.some_value:com.example.AttrKeys#SOME_KEY</export>
    <!-- ... or alternatively:
    <exports>remote.ip, remote.port, tls.cipher,
             req.headers.user-agent,
             attrs.some_value:com.example.AttrKeys#SOME_KEY</exports>
    -->
    <!-- ... or with wildcard:
    <export>req.*</export>
    -->
    <!-- ... or with custom MDC key:
    <export>remote_id=remote.id</export>
    <export>UA=req.headers.user-agent</export>
    <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>
    -->
  </appender>
  ...
</configuration>

The above configuration defines an appender called RCEA which exports the following:

  • remote.ip
    • the IP address of the remote peer,
  • tls.cipher
    • the SSL/TLS cipher suite of the connection,
  • req.headers.user-agent
    • the user agent of the client,
  • attrs.some_value
    • a custom attribute set via RequestContext.setAttr(AttributeKey.valueOf(AttrKeys.class, "SOME_KEY"), "SOME_VALUE")

... to the MDC property map and forwards the log message to the appender CONSOLE, as defined in the <appender-ref /> element.

There are three types of properties you can export using RequestContextExportingAppender.

Built-in properties

A built-in property is a common property available for most requests. See the complete list of the built-in properties and their MDC keys at BuiltInProperty. You can also use wildcard character * instead of listing all properties. For example:

  • "*"
  • "req.*"

HTTP request and response headers

When the session protocol of the current connection is HTTP, a user can export HTTP headers of the current request and response. The MDC key of the exported header is "req.headers.<lower-case header name>" or "res.headers.<lower-case header name>". For example:

  • "req.headers.user-agent"
  • "res.headers.set-cookie"

Custom attributes

A user can attach an arbitrary custom attribute to a RequestContext by using RequestContext custom attributes to store the information associated with the request being handled. RequestContextExportingAppender can export such attributes to the MDC property map as well.

Unlike other property types, you need to specify the full name of an attribute as well as its alias. For example, if you want to export an attribute com.example.Foo#ATTR_BAR with the alias bar, you need to add <export>attrs.bar:com.example.Foo#ATTR_BAR</export> to the XML configuration. The resulting MDC key to access the attribute value is attrs.bar, which follows the form of attrs.<alias>.

Using an alternative string converter for a custom attribute

By default, RequestContextExportingAppender uses Object.toString() to convert an attribute value into an MDC value. If you want an alternative string representation of an attribute value, you can define a Function class with a public no-args constructor that transforms an attribute value into a String:

package com.example;

public class SomeValue {
    public final String value;

    @Override
    public String toString() {
        // Too verbose for logging
        return "SomeValue(value=" + value + ')';
    }
}

public class MyStringifier implements Function<SomeValue, String> {
    @Override
    public String apply(SomeValue o) {
        return o.value;
    }
}

Once the Function is implemented, specify the fully-qualified class name of the Function implementation as the 3rd component of the <export /> element in the XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  ...
  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">
    ...
    <export>attrs.some_value:com.example.AttrKeys#SOME_KEY:com.example.MyStringifier</export>
    ...
  </appender>
  ...
</configuration>

Customizing MDC keys

You can override the pre-defined MDC key by prepending an alias and an equals sign (=) to it. For example, if you want to change req.id to request_id, use request_id=req.id.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  ...
  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">
    ...
    <export>remote_id=remote.id</export>
    <export>UA=req.headers.user-agent</export>
    <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>
    ...
  </appender>
  ...
</configuration>

Note that a custom MDC key cannot be used with a wildcard expression * or req.*.

Specifying a prefix for MDC keys

You can specify a prefix for MDC keys using the <prefix> element. If you want to add a prefix armeria. to all MDC keys, you need to add <prefix>armeria.</prefix> to the XML configuration.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  ...
  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">
    ...
    <!-- set the prefix of MDC keys -->
    <prefix>armeria.</prefix>
    <export>remote.id</export>
    <export>req.headers.user-agent</export>
    <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>
    ...
  </appender>
  ...
</configuration>

When you want to specify a different prefix, you can define <prefix>, <export>, and <exports> elements in the <exportGroup> element.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  ...
  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">
    ...
    <!-- set the prefix of exports which is not wrapped with the <exportGroup> element -->
    <prefix>armeria.</prefix>
    <export>remote.id</export>
    <export>req.headers.user-agent</export>
    ...
    <exportGroup>
      <!-- set the prefix of exports in this <exportGroup> -->
      <prefix>some_prefix.</prefix>
      <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>
      ...
    </exportGroup>
    <exportGroup>
      <!-- if <prefix> is not defined, no prefix is added to exports -->
      <export>tracking_id=attr:com.example.AttrKeys#TRACKING_ID_KEY</export>
      ...
    </exportGroup>
  </appender>
  ...
</configuration>