1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.filter;
18
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import org.apache.logging.log4j.Level;
25 import org.apache.logging.log4j.Marker;
26 import org.apache.logging.log4j.core.Filter;
27 import org.apache.logging.log4j.core.LogEvent;
28 import org.apache.logging.log4j.core.Logger;
29 import org.apache.logging.log4j.core.config.Node;
30 import org.apache.logging.log4j.core.config.plugins.Plugin;
31 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
32 import org.apache.logging.log4j.core.config.plugins.PluginElement;
33 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
34 import org.apache.logging.log4j.core.util.KeyValuePair;
35 import org.apache.logging.log4j.message.Message;
36 import org.apache.logging.log4j.message.StructuredDataMessage;
37 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
38 import org.apache.logging.log4j.util.PerformanceSensitive;
39 import org.apache.logging.log4j.util.StringBuilders;
40
41
42
43
44 @Plugin(name = "StructuredDataFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
45 @PerformanceSensitive("allocation")
46 public final class StructuredDataFilter extends MapFilter {
47
48 private static final int MAX_BUFFER_SIZE = 2048;
49 private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
50
51 private StructuredDataFilter(final Map<String, List<String>> map, final boolean oper, final Result onMatch,
52 final Result onMismatch) {
53 super(map, oper, onMatch, onMismatch);
54 }
55
56 @Override
57 public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
58 final Throwable t) {
59 if (msg instanceof StructuredDataMessage) {
60 return filter((StructuredDataMessage) msg);
61 }
62 return Result.NEUTRAL;
63 }
64
65 @Override
66 public Result filter(final LogEvent event) {
67 final Message msg = event.getMessage();
68 if (msg instanceof StructuredDataMessage) {
69 return filter((StructuredDataMessage) msg);
70 }
71 return super.filter(event);
72 }
73
74 protected Result filter(final StructuredDataMessage message) {
75 boolean match = false;
76 final IndexedReadOnlyStringMap map = getStringMap();
77 for (int i = 0; i < map.size(); i++) {
78 final StringBuilder toMatch = getValue(message, map.getKeyAt(i));
79 if (toMatch != null) {
80 match = listContainsValue((List<String>) map.getValueAt(i), toMatch);
81 } else {
82 match = false;
83 }
84 if ((!isAnd() && match) || (isAnd() && !match)) {
85 break;
86 }
87 }
88 return match ? onMatch : onMismatch;
89 }
90
91 private StringBuilder getValue(final StructuredDataMessage data, final String key) {
92 final StringBuilder sb = getStringBuilder();
93 if (key.equalsIgnoreCase("id")) {
94 data.getId().formatTo(sb);
95 return sb;
96 } else if (key.equalsIgnoreCase("id.name")) {
97 return appendOrNull(data.getId().getName(), sb);
98 } else if (key.equalsIgnoreCase("type")) {
99 return appendOrNull(data.getType(), sb);
100 } else if (key.equalsIgnoreCase("message")) {
101 data.formatTo(sb);
102 return sb;
103 } else {
104 return appendOrNull(data.get(key), sb);
105 }
106 }
107
108 private StringBuilder getStringBuilder() {
109 StringBuilder result = threadLocalStringBuilder.get();
110 if (result == null) {
111 result = new StringBuilder();
112 threadLocalStringBuilder.set(result);
113 }
114 StringBuilders.trimToMaxSize(result, MAX_BUFFER_SIZE);
115 result.setLength(0);
116 return result;
117 }
118
119 private StringBuilder appendOrNull(final String value, final StringBuilder sb) {
120 if (value == null) {
121 return null;
122 }
123 sb.append(value);
124 return sb;
125 }
126
127 private boolean listContainsValue(final List<String> candidates, final StringBuilder toMatch) {
128 if (toMatch == null) {
129 for (int i = 0; i < candidates.size(); i++) {
130 final String candidate = candidates.get(i);
131 if (candidate == null) {
132 return true;
133 }
134 }
135 } else {
136 for (int i = 0; i < candidates.size(); i++) {
137 final String candidate = candidates.get(i);
138 if (candidate == null) {
139 return false;
140 }
141 if (StringBuilders.equals(candidate, 0, candidate.length(), toMatch, 0, toMatch.length())) {
142 return true;
143 }
144 }
145 }
146 return false;
147 }
148
149
150
151
152
153
154
155
156
157
158 @PluginFactory
159 public static StructuredDataFilter createFilter(
160 @PluginElement("Pairs") final KeyValuePair[] pairs,
161 @PluginAttribute("operator") final String oper,
162 @PluginAttribute("onMatch") final Result match,
163 @PluginAttribute("onMismatch") final Result mismatch) {
164 if (pairs == null || pairs.length == 0) {
165 LOGGER.error("keys and values must be specified for the StructuredDataFilter");
166 return null;
167 }
168 final Map<String, List<String>> map = new HashMap<>();
169 for (final KeyValuePair pair : pairs) {
170 final String key = pair.getKey();
171 if (key == null) {
172 LOGGER.error("A null key is not valid in MapFilter");
173 continue;
174 }
175 final String value = pair.getValue();
176 if (value == null) {
177 LOGGER.error("A null value for key " + key + " is not allowed in MapFilter");
178 continue;
179 }
180 List<String> list = map.get(pair.getKey());
181 if (list != null) {
182 list.add(value);
183 } else {
184 list = new ArrayList<>();
185 list.add(value);
186 map.put(pair.getKey(), list);
187 }
188 }
189 if (map.isEmpty()) {
190 LOGGER.error("StructuredDataFilter is not configured with any valid key value pairs");
191 return null;
192 }
193 final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
194 return new StructuredDataFilter(map, isAnd, match, mismatch);
195 }
196 }