1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.nio.charset.Charset;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24
25 import org.apache.logging.log4j.core.LogEvent;
26 import org.apache.logging.log4j.core.config.Configuration;
27 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
28 import org.apache.logging.log4j.core.config.plugins.PluginElement;
29 import org.apache.logging.log4j.core.impl.MutableLogEvent;
30 import org.apache.logging.log4j.core.jackson.XmlConstants;
31 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
32 import org.apache.logging.log4j.core.util.KeyValuePair;
33 import org.apache.logging.log4j.core.util.StringBuilderWriter;
34 import org.apache.logging.log4j.util.Strings;
35
36 import com.fasterxml.jackson.annotation.JsonAnyGetter;
37 import com.fasterxml.jackson.annotation.JsonRootName;
38 import com.fasterxml.jackson.annotation.JsonUnwrapped;
39 import com.fasterxml.jackson.core.JsonGenerationException;
40 import com.fasterxml.jackson.databind.JsonMappingException;
41 import com.fasterxml.jackson.databind.ObjectWriter;
42 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
43
44 abstract class AbstractJacksonLayout extends AbstractStringLayout {
45
46 protected static final String DEFAULT_EOL = "\r\n";
47 protected static final String COMPACT_EOL = Strings.EMPTY;
48
49 public static abstract class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B> {
50
51 @PluginBuilderAttribute
52 private boolean eventEol;
53
54 @PluginBuilderAttribute
55 private boolean compact;
56
57 @PluginBuilderAttribute
58 private boolean complete;
59
60 @PluginBuilderAttribute
61 private boolean locationInfo;
62
63 @PluginBuilderAttribute
64 private boolean properties;
65
66 @PluginBuilderAttribute
67 private boolean includeStacktrace = true;
68
69 @PluginBuilderAttribute
70 private boolean stacktraceAsString = false;
71
72 @PluginBuilderAttribute
73 private boolean includeNullDelimiter = false;
74
75 @PluginElement("AdditionalField")
76 private KeyValuePair[] additionalFields;
77
78 protected String toStringOrNull(final byte[] header) {
79 return header == null ? null : new String(header, Charset.defaultCharset());
80 }
81
82 public boolean getEventEol() {
83 return eventEol;
84 }
85
86 public boolean isCompact() {
87 return compact;
88 }
89
90 public boolean isComplete() {
91 return complete;
92 }
93
94 public boolean isLocationInfo() {
95 return locationInfo;
96 }
97
98 public boolean isProperties() {
99 return properties;
100 }
101
102
103
104
105
106 public boolean isIncludeStacktrace() {
107 return includeStacktrace;
108 }
109
110 public boolean isStacktraceAsString() {
111 return stacktraceAsString;
112 }
113
114 public boolean isIncludeNullDelimiter() { return includeNullDelimiter; }
115
116 public KeyValuePair[] getAdditionalFields() {
117 return additionalFields;
118 }
119
120 public B setEventEol(final boolean eventEol) {
121 this.eventEol = eventEol;
122 return asBuilder();
123 }
124
125 public B setCompact(final boolean compact) {
126 this.compact = compact;
127 return asBuilder();
128 }
129
130 public B setComplete(final boolean complete) {
131 this.complete = complete;
132 return asBuilder();
133 }
134
135 public B setLocationInfo(final boolean locationInfo) {
136 this.locationInfo = locationInfo;
137 return asBuilder();
138 }
139
140 public B setProperties(final boolean properties) {
141 this.properties = properties;
142 return asBuilder();
143 }
144
145
146
147
148
149
150 public B setIncludeStacktrace(final boolean includeStacktrace) {
151 this.includeStacktrace = includeStacktrace;
152 return asBuilder();
153 }
154
155
156
157
158
159
160 public B setStacktraceAsString(final boolean stacktraceAsString) {
161 this.stacktraceAsString = stacktraceAsString;
162 return asBuilder();
163 }
164
165
166
167
168
169
170 public B setIncludeNullDelimiter(final boolean includeNullDelimiter) {
171 this.includeNullDelimiter = includeNullDelimiter;
172 return asBuilder();
173 }
174
175
176
177
178
179
180 public B setAdditionalFields(KeyValuePair[] additionalFields) {
181 this.additionalFields = additionalFields;
182 return asBuilder();
183 }
184 }
185
186 protected final String eol;
187 protected final ObjectWriter objectWriter;
188 protected final boolean compact;
189 protected final boolean complete;
190 protected final boolean includeNullDelimiter;
191 protected final ResolvableKeyValuePair[] additionalFields;
192
193 @Deprecated
194 protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
195 final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
196 final Serializer footerSerializer) {
197 this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false);
198 }
199
200 @Deprecated
201 protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
202 final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
203 final Serializer footerSerializer, final boolean includeNullDelimiter) {
204 this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, includeNullDelimiter, null);
205 }
206
207 protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
208 final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
209 final Serializer footerSerializer, final boolean includeNullDelimiter,
210 final KeyValuePair[] additionalFields) {
211 super(config, charset, headerSerializer, footerSerializer);
212 this.objectWriter = objectWriter;
213 this.compact = compact;
214 this.complete = complete;
215 this.eol = compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
216 this.includeNullDelimiter = includeNullDelimiter;
217 this.additionalFields = prepareAdditionalFields(config, additionalFields);
218 }
219
220 protected static boolean valueNeedsLookup(final String value) {
221 return value != null && value.contains("${");
222 }
223
224 private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, final KeyValuePair[] additionalFields) {
225 if (additionalFields == null || additionalFields.length == 0) {
226
227 return new ResolvableKeyValuePair[0];
228 }
229
230
231 final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length];
232
233 for (int i = 0; i < additionalFields.length; i++) {
234 ResolvableKeyValuePair resolvable = resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]);
235
236
237 if (config == null && resolvable.valueNeedsLookup) {
238 throw new IllegalArgumentException("configuration needs to be set when there are additional fields with variables");
239 }
240 }
241
242 return resolvableFields;
243 }
244
245
246
247
248
249
250
251 @Override
252 public String toSerializable(final LogEvent event) {
253 final StringBuilderWriter writer = new StringBuilderWriter();
254 try {
255 toSerializable(event, writer);
256 return writer.toString();
257 } catch (final IOException e) {
258
259 LOGGER.error(e);
260 return Strings.EMPTY;
261 }
262 }
263
264 private static LogEvent convertMutableToLog4jEvent(final LogEvent event) {
265
266
267
268 return event instanceof MutableLogEvent
269 ? ((MutableLogEvent) event).createMemento()
270 : event;
271 }
272
273 protected Object wrapLogEvent(final LogEvent event) {
274 if (additionalFields.length > 0) {
275
276 Map<String, String> additionalFieldsMap = resolveAdditionalFields(event);
277
278 return new LogEventWithAdditionalFields(event, additionalFieldsMap);
279 } else {
280
281 return event;
282 }
283 }
284
285 private Map<String, String> resolveAdditionalFields(LogEvent logEvent) {
286
287 final Map<String, String> additionalFieldsMap = new LinkedHashMap<>(additionalFields.length);
288 final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor();
289
290
291 for (ResolvableKeyValuePair pair : additionalFields) {
292 if (pair.valueNeedsLookup) {
293
294 additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value));
295 } else {
296
297 additionalFieldsMap.put(pair.key, pair.value);
298 }
299 }
300
301 return additionalFieldsMap;
302 }
303
304 public void toSerializable(final LogEvent event, final Writer writer)
305 throws JsonGenerationException, JsonMappingException, IOException {
306 objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event)));
307 writer.write(eol);
308 if (includeNullDelimiter) {
309 writer.write('\0');
310 }
311 markEvent();
312 }
313
314 @JsonRootName(XmlConstants.ELT_EVENT)
315 @JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT)
316 public static class LogEventWithAdditionalFields {
317
318 private final Object logEvent;
319 private final Map<String, String> additionalFields;
320
321 public LogEventWithAdditionalFields(Object logEvent, Map<String, String> additionalFields) {
322 this.logEvent = logEvent;
323 this.additionalFields = additionalFields;
324 }
325
326 @JsonUnwrapped
327 public Object getLogEvent() {
328 return logEvent;
329 }
330
331 @JsonAnyGetter
332 @SuppressWarnings("unused")
333 public Map<String, String> getAdditionalFields() {
334 return additionalFields;
335 }
336 }
337
338 protected static class ResolvableKeyValuePair {
339
340 final String key;
341 final String value;
342 final boolean valueNeedsLookup;
343
344 ResolvableKeyValuePair(KeyValuePair pair) {
345 this.key = pair.getKey();
346 this.value = pair.getValue();
347 this.valueNeedsLookup = AbstractJacksonLayout.valueNeedsLookup(this.value);
348 }
349 }
350 }