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.filter;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.logging.log4j.Level;
025import org.apache.logging.log4j.Marker;
026import org.apache.logging.log4j.core.Filter;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.Logger;
029import org.apache.logging.log4j.core.config.Node;
030import org.apache.logging.log4j.core.config.plugins.Plugin;
031import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginElement;
033import org.apache.logging.log4j.core.config.plugins.PluginFactory;
034import org.apache.logging.log4j.core.util.KeyValuePair;
035import org.apache.logging.log4j.message.Message;
036import org.apache.logging.log4j.message.StructuredDataMessage;
037
038/**
039 * Filter based on data in a StructuredDataMessage.
040 */
041@Plugin(name = "StructuredDataFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
042public final class StructuredDataFilter extends MapFilter {
043
044    private static final long serialVersionUID = 1L;
045
046    private StructuredDataFilter(final Map<String, List<String>> map, final boolean oper, final Result onMatch,
047                                 final Result onMismatch) {
048        super(map, oper, onMatch, onMismatch);
049    }
050
051    @Override
052    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
053                         final Throwable t) {
054        if (msg instanceof StructuredDataMessage) {
055            return filter((StructuredDataMessage) msg);
056        }
057        return Result.NEUTRAL;
058    }
059
060    @Override
061    public Result filter(final LogEvent event) {
062        final Message msg = event.getMessage();
063        if (msg instanceof StructuredDataMessage) {
064            return filter((StructuredDataMessage) msg);
065        }
066        return super.filter(event);
067    }
068
069    protected Result filter(final StructuredDataMessage message) {
070        boolean match = false;
071        for (final Map.Entry<String, List<String>> entry : getMap().entrySet()) {
072            final String toMatch = getValue(message, entry.getKey());
073            if (toMatch != null) {
074                match = entry.getValue().contains(toMatch);
075            } else {
076                match = false;
077            }
078            if ((!isAnd() && match) || (isAnd() && !match)) {
079                break;
080            }
081        }
082        return match ? onMatch : onMismatch;
083    }
084
085    private String getValue(final StructuredDataMessage data, final String key) {
086        if (key.equalsIgnoreCase("id")) {
087            return data.getId().toString();
088        } else if (key.equalsIgnoreCase("id.name")) {
089            return data.getId().getName();
090        } else if (key.equalsIgnoreCase("type")) {
091            return data.getType();
092        } else if (key.equalsIgnoreCase("message")) {
093            return data.getFormattedMessage();
094        } else {
095            return data.getData().get(key);
096        }
097    }
098
099    /**
100     * Create the StructuredDataFilter.
101     * @param pairs Key and value pairs.
102     * @param oper The operator to perform. If not "or" the operation will be an "and".
103     * @param match The action to perform on a match.
104     * @param mismatch The action to perform on a mismatch.
105     * @return The StructuredDataFilter.
106     */
107    @PluginFactory
108    public static StructuredDataFilter createFilter(
109            @PluginElement("Pairs") final KeyValuePair[] pairs,
110            @PluginAttribute("operator") final String oper,
111            @PluginAttribute("onMatch") final Result match,
112            @PluginAttribute("onMismatch") final Result mismatch) {
113        if (pairs == null || pairs.length == 0) {
114            LOGGER.error("keys and values must be specified for the StructuredDataFilter");
115            return null;
116        }
117        final Map<String, List<String>> map = new HashMap<String, List<String>>();
118        for (final KeyValuePair pair : pairs) {
119            final String key = pair.getKey();
120            if (key == null) {
121                LOGGER.error("A null key is not valid in MapFilter");
122                continue;
123            }
124            final String value = pair.getValue();
125            if (value == null) {
126                LOGGER.error("A null value for key " + key + " is not allowed in MapFilter");
127                continue;
128            }
129            List<String> list = map.get(pair.getKey());
130            if (list != null) {
131                list.add(value);
132            } else {
133                list = new ArrayList<String>();
134                list.add(value);
135                map.put(pair.getKey(), list);
136            }
137        }
138        if (map.isEmpty()) {
139            LOGGER.error("StructuredDataFilter is not configured with any valid key value pairs");
140            return null;
141        }
142        final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
143        return new StructuredDataFilter(map, isAnd, match, mismatch);
144    }
145}