View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
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.Level;
26  import org.apache.logging.log4j.Marker;
27  import org.apache.logging.log4j.ThreadContext;
28  import org.apache.logging.log4j.core.LogEvent;
29  import org.apache.logging.log4j.core.config.Configuration;
30  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
31  import org.apache.logging.log4j.core.config.plugins.PluginElement;
32  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
33  import org.apache.logging.log4j.core.impl.ThrowableProxy;
34  import org.apache.logging.log4j.core.jackson.XmlConstants;
35  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
36  import org.apache.logging.log4j.core.time.Instant;
37  import org.apache.logging.log4j.core.util.KeyValuePair;
38  import org.apache.logging.log4j.core.util.StringBuilderWriter;
39  import org.apache.logging.log4j.message.Message;
40  import org.apache.logging.log4j.util.ReadOnlyStringMap;
41  import org.apache.logging.log4j.util.Strings;
42  
43  import com.fasterxml.jackson.annotation.JsonAnyGetter;
44  import com.fasterxml.jackson.annotation.JsonIgnore;
45  import com.fasterxml.jackson.annotation.JsonRootName;
46  import com.fasterxml.jackson.annotation.JsonUnwrapped;
47  import com.fasterxml.jackson.core.JsonGenerationException;
48  import com.fasterxml.jackson.databind.JsonMappingException;
49  import com.fasterxml.jackson.databind.ObjectWriter;
50  import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
51  
52  abstract class AbstractJacksonLayout extends AbstractStringLayout {
53  
54      protected static final String DEFAULT_EOL = "\r\n";
55      protected static final String COMPACT_EOL = Strings.EMPTY;
56  
57      public static abstract class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B> {
58  
59          @PluginBuilderAttribute
60          private boolean eventEol;
61  
62          @PluginBuilderAttribute
63          private String endOfLine;
64  
65          @PluginBuilderAttribute
66          private boolean compact;
67  
68          @PluginBuilderAttribute
69          private boolean complete;
70  
71          @PluginBuilderAttribute
72          private boolean locationInfo;
73  
74          @PluginBuilderAttribute
75          private boolean properties;
76  
77          @PluginBuilderAttribute
78          private boolean includeStacktrace = true;
79  
80          @PluginBuilderAttribute
81          private boolean stacktraceAsString = false;
82  
83          @PluginBuilderAttribute
84          private boolean includeNullDelimiter = false;
85  
86          @PluginBuilderAttribute
87          private boolean includeTimeMillis = false;
88  
89          @PluginElement("AdditionalField")
90          private KeyValuePair[] additionalFields;
91  
92          protected String toStringOrNull(final byte[] header) {
93              return header == null ? null : new String(header, Charset.defaultCharset());
94          }
95  
96          public boolean getEventEol() {
97              return eventEol;
98          }
99  
100         public String getEndOfLine() {
101             return endOfLine;
102         }
103 
104         public boolean isCompact() {
105             return compact;
106         }
107 
108         public boolean isComplete() {
109             return complete;
110         }
111 
112         public boolean isLocationInfo() {
113             return locationInfo;
114         }
115 
116         public boolean isProperties() {
117             return properties;
118         }
119 
120         /**
121          * If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true".
122          * @return If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true".
123          */
124         public boolean isIncludeStacktrace() {
125             return includeStacktrace;
126         }
127 
128         public boolean isStacktraceAsString() {
129             return stacktraceAsString;
130         }
131 
132         public boolean isIncludeNullDelimiter() { return includeNullDelimiter; }
133 
134         public boolean isIncludeTimeMillis() {
135             return includeTimeMillis;
136         }
137 
138         public KeyValuePair[] getAdditionalFields() {
139             return additionalFields;
140         }
141 
142         public B setEventEol(final boolean eventEol) {
143             this.eventEol = eventEol;
144             return asBuilder();
145         }
146 
147         public B setEndOfLine(final String endOfLine) {
148             this.endOfLine = endOfLine;
149             return asBuilder();
150         }
151 
152         public B setCompact(final boolean compact) {
153             this.compact = compact;
154             return asBuilder();
155         }
156 
157         public B setComplete(final boolean complete) {
158             this.complete = complete;
159             return asBuilder();
160         }
161 
162         public B setLocationInfo(final boolean locationInfo) {
163             this.locationInfo = locationInfo;
164             return asBuilder();
165         }
166 
167         public B setProperties(final boolean properties) {
168             this.properties = properties;
169             return asBuilder();
170         }
171 
172         /**
173          * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true".
174          * @param includeStacktrace If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true".
175          * @return this builder
176          */
177         public B setIncludeStacktrace(final boolean includeStacktrace) {
178             this.includeStacktrace = includeStacktrace;
179             return asBuilder();
180         }
181 
182         /**
183          * Whether to format the stacktrace as a string, and not a nested object (optional, defaults to false).
184          *
185          * @return this builder
186          */
187         public B setStacktraceAsString(final boolean stacktraceAsString) {
188             this.stacktraceAsString = stacktraceAsString;
189             return asBuilder();
190         }
191 
192         /**
193          * Whether to include NULL byte as delimiter after each event (optional, default to false).
194          *
195          * @return this builder
196          */
197         public B setIncludeNullDelimiter(final boolean includeNullDelimiter) {
198             this.includeNullDelimiter = includeNullDelimiter;
199             return asBuilder();
200         }
201 
202         /**
203          * Whether to include the timestamp (in addition to the Instant) (optional, default to false).
204          *
205          * @return this builder
206          */
207         public B setIncludeTimeMillis(final boolean includeTimeMillis) {
208             this.includeTimeMillis = includeTimeMillis;
209             return asBuilder();
210         }
211 
212         /**
213          * Additional fields to set on each log event.
214          *
215          * @return this builder
216          */
217         public B setAdditionalFields(final KeyValuePair[] additionalFields) {
218             this.additionalFields = additionalFields;
219             return asBuilder();
220         }
221     }
222 
223     protected final String eol;
224     protected final ObjectWriter objectWriter;
225     protected final boolean compact;
226     protected final boolean complete;
227     protected final boolean includeNullDelimiter;
228     protected final ResolvableKeyValuePair[] additionalFields;
229 
230     @Deprecated
231     protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
232             final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
233             final Serializer footerSerializer) {
234         this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false);
235     }
236 
237     @Deprecated
238     protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
239             final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
240             final Serializer footerSerializer, final boolean includeNullDelimiter) {
241         this(config, objectWriter, charset, compact, complete, eventEol, null, headerSerializer, footerSerializer, includeNullDelimiter, null);
242     }
243 
244     protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
245             final boolean compact, final boolean complete, final boolean eventEol, final String endOfLine, final Serializer headerSerializer,
246             final Serializer footerSerializer, final boolean includeNullDelimiter,
247             final KeyValuePair[] additionalFields) {
248         super(config, charset, headerSerializer, footerSerializer);
249         this.objectWriter = objectWriter;
250         this.compact = compact;
251         this.complete = complete;
252         this.eol = endOfLine != null ? endOfLine : compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
253         this.includeNullDelimiter = includeNullDelimiter;
254         this.additionalFields = prepareAdditionalFields(config, additionalFields);
255     }
256 
257     protected static boolean valueNeedsLookup(final String value) {
258         return value != null && value.contains("${");
259     }
260 
261     private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, final KeyValuePair[] additionalFields) {
262         if (additionalFields == null || additionalFields.length == 0) {
263             // No fields set
264             return new ResolvableKeyValuePair[0];
265         }
266 
267         // Convert to specific class which already determines whether values needs lookup during serialization
268         final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length];
269 
270         for (int i = 0; i < additionalFields.length; i++) {
271             final ResolvableKeyValuePair resolvable = resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]);
272 
273             // Validate
274             if (config == null && resolvable.valueNeedsLookup) {
275                 throw new IllegalArgumentException("configuration needs to be set when there are additional fields with variables");
276             }
277         }
278 
279         return resolvableFields;
280     }
281 
282     /**
283      * Formats a {@link org.apache.logging.log4j.core.LogEvent}.
284      *
285      * @param event The LogEvent.
286      * @return The XML representation of the LogEvent.
287      */
288     @Override
289     public String toSerializable(final LogEvent event) {
290         final StringBuilderWriter writer = new StringBuilderWriter();
291         try {
292             toSerializable(event, writer);
293             return writer.toString();
294         } catch (final IOException e) {
295             // Should this be an ISE or IAE?
296             LOGGER.error(e);
297             return Strings.EMPTY;
298         }
299     }
300 
301     private static LogEvent convertMutableToLog4jEvent(final LogEvent event) {
302         // TODO Jackson-based layouts have certain filters set up for Log4jLogEvent.
303         // TODO Need to set up the same filters for MutableLogEvent but don't know how...
304         // This is a workaround.
305         return event instanceof Log4jLogEvent ? event : Log4jLogEvent.createMemento(event);
306     }
307 
308     protected Object wrapLogEvent(final LogEvent event) {
309         if (additionalFields.length > 0) {
310             // Construct map for serialization - note that we are intentionally using original LogEvent
311             final Map<String, String> additionalFieldsMap = resolveAdditionalFields(event);
312             // This class combines LogEvent with AdditionalFields during serialization
313             return new LogEventWithAdditionalFields(event, additionalFieldsMap);
314         } else if (event instanceof Message) {
315             // If the LogEvent implements the Messagee interface Jackson will not treat is as a LogEvent.
316             return new ReadOnlyLogEventWrapper(event);
317         } else {
318             // No additional fields, return original object
319             return event;
320         }
321     }
322 
323     private Map<String, String> resolveAdditionalFields(final LogEvent logEvent) {
324         // Note: LinkedHashMap retains order
325         final Map<String, String> additionalFieldsMap = new LinkedHashMap<>(additionalFields.length);
326         final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor();
327 
328         // Go over each field
329         for (final ResolvableKeyValuePair pair : additionalFields) {
330             if (pair.valueNeedsLookup) {
331                 // Resolve value
332                 additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value));
333             } else {
334                 // Plain text value
335                 additionalFieldsMap.put(pair.key, pair.value);
336             }
337         }
338 
339         return additionalFieldsMap;
340     }
341 
342     public void toSerializable(final LogEvent event, final Writer writer)
343             throws JsonGenerationException, JsonMappingException, IOException {
344         objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event)));
345         writer.write(eol);
346         if (includeNullDelimiter) {
347             writer.write('\0');
348         }
349         markEvent();
350     }
351 
352     @JsonRootName(XmlConstants.ELT_EVENT)
353     @JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT)
354     public static class LogEventWithAdditionalFields {
355 
356         private final Object logEvent;
357         private final Map<String, String> additionalFields;
358 
359         public LogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
360             this.logEvent = logEvent;
361             this.additionalFields = additionalFields;
362         }
363 
364         @JsonUnwrapped
365         public Object getLogEvent() {
366             return logEvent;
367         }
368 
369         @JsonAnyGetter
370         @SuppressWarnings("unused")
371         public Map<String, String> getAdditionalFields() {
372             return additionalFields;
373         }
374     }
375 
376     protected static class ResolvableKeyValuePair {
377 
378         final String key;
379         final String value;
380         final boolean valueNeedsLookup;
381 
382         ResolvableKeyValuePair(final KeyValuePair pair) {
383             this.key = pair.getKey();
384             this.value = pair.getValue();
385             this.valueNeedsLookup = AbstractJacksonLayout.valueNeedsLookup(this.value);
386         }
387     }
388 
389     private static class ReadOnlyLogEventWrapper implements LogEvent {
390 
391         @JsonIgnore
392         private final LogEvent event;
393 
394         public ReadOnlyLogEventWrapper(LogEvent event) {
395             this.event = event;
396         }
397 
398         @Override
399         public LogEvent toImmutable() {
400             return event.toImmutable();
401         }
402 
403         @Override
404         public Map<String, String> getContextMap() {
405             return event.getContextMap();
406         }
407 
408         @Override
409         public ReadOnlyStringMap getContextData() {
410             return event.getContextData();
411         }
412 
413         @Override
414         public ThreadContext.ContextStack getContextStack() {
415             return event.getContextStack();
416         }
417 
418         @Override
419         public String getLoggerFqcn() {
420             return event.getLoggerFqcn();
421         }
422 
423         @Override
424         public Level getLevel() {
425             return event.getLevel();
426         }
427 
428         @Override
429         public String getLoggerName() {
430             return event.getLoggerName();
431         }
432 
433         @Override
434         public Marker getMarker() {
435             return event.getMarker();
436         }
437 
438         @Override
439         public Message getMessage() {
440             return event.getMessage();
441         }
442 
443         @Override
444         public long getTimeMillis() {
445             return event.getTimeMillis();
446         }
447 
448         @Override
449         public Instant getInstant() {
450             return event.getInstant();
451         }
452 
453         @Override
454         public StackTraceElement getSource() {
455             return event.getSource();
456         }
457 
458         @Override
459         public String getThreadName() {
460             return event.getThreadName();
461         }
462 
463         @Override
464         public long getThreadId() {
465             return event.getThreadId();
466         }
467 
468         @Override
469         public int getThreadPriority() {
470             return event.getThreadPriority();
471         }
472 
473         @Override
474         public Throwable getThrown() {
475             return event.getThrown();
476         }
477 
478         @Override
479         public ThrowableProxy getThrownProxy() {
480             return event.getThrownProxy();
481         }
482 
483         @Override
484         public boolean isEndOfBatch() {
485             return event.isEndOfBatch();
486         }
487 
488         @Override
489         public boolean isIncludeLocation() {
490             return event.isIncludeLocation();
491         }
492 
493         @Override
494         public void setEndOfBatch(boolean endOfBatch) {
495 
496         }
497 
498         @Override
499         public void setIncludeLocation(boolean locationRequired) {
500 
501         }
502 
503         @Override
504         public long getNanoTime() {
505             return event.getNanoTime();
506         }
507     }
508 }