import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.BiConsumer;
import org.apache.logging.log4j.util.ReadOnlyStringMap;


 * This is a simple {@link LogEventPatternConverter} implementation that
 * converts key/value pairs stored in {@link CustomField} instances which have
 * been passed as arguments.
 * <p>
 * We allow to types of addition to a log message, either <i>embedded</i>, i.e.
 * the key/value pairs appear as a list of JSON fields in the message, or as a
 * nested object where the field name has been specified as an option to this
 * converter.
@Plugin(name = "ArgsConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({ "cf" })
public class CustomFieldsConverter extends LogEventPatternConverter {

	public static final String WORD = "cf";

	private final List<String> customFieldMdcKeyNames;
	private DefaultCustomFieldsConverter converter = new DefaultCustomFieldsConverter();

	public CustomFieldsConverter(String[] options) {
		super(WORD, WORD);

		customFieldMdcKeyNames = options == null ? emptyList() : unmodifiableList(asList(options));
	public static CustomFieldsConverter newInstance(final String[] options) {
		return new CustomFieldsConverter(options);

	void setConverter(DefaultCustomFieldsConverter converter) {
		this.converter = converter;
	public void format(LogEvent event, StringBuilder appendTo) {
		converter.convert(appendTo, getCustomFieldsFromMdc(event), getMessageParameters(event));

	private Object[] getMessageParameters(LogEvent event) {
		Message message = event.getMessage();
		return message == null ? null : message.getParameters();

	private Map<String, String> getCustomFieldsFromMdc(LogEvent event) {
		ReadOnlyStringMap contextData = event.getContextData();
		if (contextData == null || contextData.isEmpty()) {
			return Collections.emptyMap();
		CustomFieldsMdcCollector mdcCollector = new CustomFieldsMdcCollector();
		return mdcCollector.getCustomFields();

	private class CustomFieldsMdcCollector implements BiConsumer<String, String> {
		private Map<String, String> customFields = new HashMap<>(customFieldMdcKeyNames.size());

		public void accept(String key, String value) {
			if (customFieldMdcKeyNames.contains(key)) {
				customFields.put(key, value);

		public Map<String, String> getCustomFields() {
			return customFields;