001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.appender.rewrite;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import org.apache.logging.log4j.Logger;
023import org.apache.logging.log4j.core.LogEvent;
024import org.apache.logging.log4j.core.config.plugins.Plugin;
025import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
026import org.apache.logging.log4j.core.config.plugins.PluginElement;
027import org.apache.logging.log4j.core.config.plugins.PluginFactory;
028import org.apache.logging.log4j.core.impl.Log4jLogEvent;
029import org.apache.logging.log4j.core.util.KeyValuePair;
030import org.apache.logging.log4j.message.MapMessage;
031import org.apache.logging.log4j.message.Message;
032import org.apache.logging.log4j.status.StatusLogger;
033
034/**
035 * This policy modifies events by replacing or possibly adding keys and values to the MapMessage.
036 */
037@Plugin(name = "MapRewritePolicy", category = "Core", elementType = "rewritePolicy", printObject = true)
038public final class MapRewritePolicy implements RewritePolicy {
039    /**
040     * Allow subclasses access to the status logger without creating another instance.
041     */
042    protected static final Logger LOGGER = StatusLogger.getLogger();
043
044    private final Map<String, String> map;
045
046    private final Mode mode;
047
048    private MapRewritePolicy(final Map<String, String> map, final Mode mode) {
049        this.map = map;
050        this.mode = mode;
051    }
052
053    /**
054     * Rewrite the event.
055     * @param source a logging event that may be returned or
056     * used to create a new logging event.
057     * @return The LogEvent after rewriting.
058     */
059    @Override
060    public LogEvent rewrite(final LogEvent source) {
061        final Message msg = source.getMessage();
062        if (msg == null || !(msg instanceof MapMessage)) {
063            return source;
064        }
065
066        final Map<String, String> newMap = new HashMap<>(((MapMessage) msg).getData());
067        switch (mode) {
068            case Add: {
069                newMap.putAll(map);
070                break;
071            }
072            default: {
073                for (final Map.Entry<String, String> entry : map.entrySet()) {
074                    if (newMap.containsKey(entry.getKey())) {
075                        newMap.put(entry.getKey(), entry.getValue());
076                    }
077                }
078            }
079        }
080        final MapMessage message = ((MapMessage) msg).newInstance(newMap);
081        final LogEvent result = new Log4jLogEvent.Builder(source).setMessage(message).build();
082        return result;
083    }
084
085    /**
086     * An enumeration to identify whether keys not in the MapMessage should be added or whether only existing
087     * keys should be updated.
088     */
089    public enum Mode {
090        /**
091         * Keys should be added.
092         */
093        Add,
094        /**
095         * Keys should be updated.
096         */
097        Update
098    }
099
100    @Override
101    public String toString() {
102        final StringBuilder sb = new StringBuilder();
103        sb.append("mode=").append(mode);
104        sb.append(" {");
105        boolean first = true;
106        for (final Map.Entry<String, String> entry : map.entrySet()) {
107            if (!first) {
108                sb.append(", ");
109            }
110            sb.append(entry.getKey()).append('=').append(entry.getValue());
111            first = false;
112        }
113        sb.append('}');
114        return sb.toString();
115    }
116
117    /**
118     * The factory method to create the MapRewritePolicy.
119     * @param mode The string representation of the Mode.
120     * @param pairs key/value pairs for the new Map keys and values.
121     * @return The MapRewritePolicy.
122     */
123    @PluginFactory
124    public static MapRewritePolicy createPolicy(
125            @PluginAttribute("mode") final String mode,
126            @PluginElement("KeyValuePair") final KeyValuePair[] pairs) {
127        Mode op = mode == null ? op = Mode.Add : Mode.valueOf(mode);
128        if (pairs == null || pairs.length == 0) {
129            LOGGER.error("keys and values must be specified for the MapRewritePolicy");
130            return null;
131        }
132        final Map<String, String> map = new HashMap<>();
133        for (final KeyValuePair pair : pairs) {
134            final String key = pair.getKey();
135            if (key == null) {
136                LOGGER.error("A null key is not valid in MapRewritePolicy");
137                continue;
138            }
139            final String value = pair.getValue();
140            if (value == null) {
141                LOGGER.error("A null value for key " + key + " is not allowed in MapRewritePolicy");
142                continue;
143            }
144            map.put(pair.getKey(), pair.getValue());
145        }
146        if (map.isEmpty()) {
147            LOGGER.error("MapRewritePolicy is not configured with any valid key value pairs");
148            return null;
149        }
150        return new MapRewritePolicy(map, op);
151    }
152}