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