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<String, String>(((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        if (source instanceof Log4jLogEvent) {
082            final Log4jLogEvent event = (Log4jLogEvent) source;
083            return Log4jLogEvent.createEvent(event.getLoggerName(), event.getMarker(), event.getLoggerFqcn(),
084                event.getLevel(), message, event.getThrown(), event.getThrownProxy(), event.getContextMap(), 
085                event.getContextStack(), event.getThreadName(), event.getSource(), event.getTimeMillis());
086        }
087        return new Log4jLogEvent(source.getLoggerName(), source.getMarker(), source.getLoggerFqcn(), source.getLevel(),
088            message, source.getThrown(), source.getContextMap(), source.getContextStack(), source.getThreadName(),
089            source.getSource(), source.getTimeMillis());
090    }
091
092    /**
093     * An enumeration to identify whether keys not in the MapMessage should be added or whether only existing
094     * keys should be updated.
095     */
096    public enum Mode {
097        /**
098         * Keys should be added.
099         */
100        Add,
101        /**
102         * Keys should be updated.
103         */
104        Update
105    }
106
107    @Override
108    public String toString() {
109        final StringBuilder sb = new StringBuilder();
110        sb.append("mode=").append(mode);
111        sb.append(" {");
112        boolean first = true;
113        for (final Map.Entry<String, String> entry : map.entrySet()) {
114            if (!first) {
115                sb.append(", ");
116            }
117            sb.append(entry.getKey()).append('=').append(entry.getValue());
118            first = false;
119        }
120        sb.append('}');
121        return sb.toString();
122    }
123
124    /**
125     * The factory method to create the MapRewritePolicy.
126     * @param mode The string representation of the Mode.
127     * @param pairs key/value pairs for the new Map keys and values.
128     * @return The MapRewritePolicy.
129     */
130    @PluginFactory
131    public static MapRewritePolicy createPolicy(
132            @PluginAttribute("mode") final String mode,
133            @PluginElement("KeyValuePair") final KeyValuePair[] pairs) {
134        Mode op;
135        if (mode == null) {
136            op = Mode.Add;
137        } else {
138            op = Mode.valueOf(mode);
139            if (op == null) {
140                LOGGER.error("Undefined mode " + mode);
141                return null;
142            }
143        }
144        if (pairs == null || pairs.length == 0) {
145            LOGGER.error("keys and values must be specified for the MapRewritePolicy");
146            return null;
147        }
148        final Map<String, String> map = new HashMap<String, String>();
149        for (final KeyValuePair pair : pairs) {
150            final String key = pair.getKey();
151            if (key == null) {
152                LOGGER.error("A null key is not valid in MapRewritePolicy");
153                continue;
154            }
155            final String value = pair.getValue();
156            if (value == null) {
157                LOGGER.error("A null value for key " + key + " is not allowed in MapRewritePolicy");
158                continue;
159            }
160            map.put(pair.getKey(), pair.getValue());
161        }
162        if (map.isEmpty()) {
163            LOGGER.error("MapRewritePolicy is not configured with any valid key value pairs");
164            return null;
165        }
166        return new MapRewritePolicy(map, op);
167    }
168}