0

I have a Java Akka application and I want to set a separate MDC context for each message handling based on information inside the message, for example I have the following base interface for all messages:

public interface IdMessage {
    String getId();
}

Also I have the following base actor for all actors:

public class BaseActor extends AbstractActor {

    private final DiagnosticLoggingAdapter log = Logging.apply(this);

    @Override
    public void aroundReceive(PartialFunction<Object, BoxedUnit> receive, Object msg) {
        if (msg instanceof IdMessage) {
            final Map<String, Object> originalMDC = log.getMDC();
            log.setMDC(ImmutableMap.of("id", ((IdMessage) msg).getId()));
            try {
                super.aroundReceive(receive, msg);
            } finally {
                if (originalMDC != null) {
                    log.setMDC(originalMDC);
                } else {
                    log.clearMDC();
                }
            }
        } else {
            super.aroundReceive(receive, msg);
        }
    }
}

And the actual actor implementation:

public class SomeActor extends BaseActor {
    SomeActor() {
    receive(ReceiveBuilder
                    .match(SomeMessage.class, message -> {
                        ...
                     }
    }
}

I would like to make sure that all logs entries inside SomeActor#receive() will have MDC context set in the BaseActor. To make this work SomeActor#receice() need to be executed in the same thread as BaseActor#aroundReceive() method.

I didn't find any information about the behaviour of aroundReceive - is that going to be always executed in the same thread as the actual receive method? Based on my testing it's always executed in the same thread.

erkfel
  • 1,588
  • 2
  • 17
  • 29

1 Answers1

2

I was able to figure out the proper implementation by myself and would like to share it in case someone face with the same issue.

The aroundReceive is going to be executed in the same thread as receive, so this is the right place to set MDC context.

I used org.slf4j.MDC for setting the MDC context, here is the full code:

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import com.google.common.collect.ImmutableMap;

import akka.actor.AbstractActor;
import scala.PartialFunction;
import scala.runtime.BoxedUnit;

public class BaseActor extends AbstractActor {

    private final Logger log = LoggerFactory.getLogger(BaseActor.class);

    @Override
    public void aroundReceive(PartialFunction<Object, BoxedUnit> receive, Object msg) {
        if (msg instanceof IdMessage) {
            final Map<String, Object> originalMDC = log.getMDC();
            MDC.setContextMap(ImmutableMap.of("id", ((IdMessage) msg).getId()));
            try {
                super.aroundReceive(receive, msg);
            } finally {
                if (originalMDC != null) {
                    MDC.setContextMap(originalMDC);
                } else {
                    MDC.clear();
                }
            }
        } else {
            super.aroundReceive(receive, msg);
        }
    }
}

With that implementation of BaseActor all log entries in receive are logged with a proper MDC context. Additional information could be found in this interesting blog post (with Scala implementation).

Note: I was not able to reach the same functionality with Akka DiagnosticLoggingAdapter although it has methods to set MDC context.

erkfel
  • 1,588
  • 2
  • 17
  • 29
  • The reasons why `DiagnosticLoggingAdapter` doesn't do the trick are explained in [this answer](http://stackoverflow.com/a/31237679/843660). As for overriding `aroundReceive`, this is marked as internal API and only works if you put your class in package `akka.actor`, which is a hack. See [this answer](http://stackoverflow.com/a/26723452/843660). Not sure what can be done in Java though. – dskrvk Sep 04 '16 at 20:03