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.nio.charset.Charset;
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.GregorianCalendar;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.SortedMap;
27  import java.util.TreeMap;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  
31  import org.apache.logging.log4j.Level;
32  import org.apache.logging.log4j.LoggingException;
33  import org.apache.logging.log4j.core.LogEvent;
34  import org.apache.logging.log4j.core.appender.TLSSyslogFrame;
35  import org.apache.logging.log4j.core.config.Configuration;
36  import org.apache.logging.log4j.core.config.plugins.Plugin;
37  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
38  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
39  import org.apache.logging.log4j.core.config.plugins.PluginElement;
40  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
41  import org.apache.logging.log4j.core.helpers.Booleans;
42  import org.apache.logging.log4j.core.helpers.Charsets;
43  import org.apache.logging.log4j.core.helpers.Integers;
44  import org.apache.logging.log4j.core.helpers.NetUtils;
45  import org.apache.logging.log4j.core.helpers.Strings;
46  import org.apache.logging.log4j.core.net.Facility;
47  import org.apache.logging.log4j.core.net.Priority;
48  import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
49  import org.apache.logging.log4j.core.pattern.PatternConverter;
50  import org.apache.logging.log4j.core.pattern.PatternFormatter;
51  import org.apache.logging.log4j.core.pattern.PatternParser;
52  import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
53  import org.apache.logging.log4j.message.Message;
54  import org.apache.logging.log4j.message.StructuredDataId;
55  import org.apache.logging.log4j.message.StructuredDataMessage;
56  
57  
58  /**
59   * Formats a log event in accordance with RFC 5424.
60   *
61   * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>
62   */
63  @Plugin(name = "RFC5424Layout", category = "Core", elementType = "layout", printObject = true)
64  public class RFC5424Layout extends AbstractStringLayout {
65  
66      private static final String LF = "\n";
67  
68      /**
69       * Not a very good default - it is the Apache Software Foundation's enterprise number.
70       */
71      public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
72      /**
73       * The default event id.
74       */
75      public static final String DEFAULT_ID = "Audit";
76      /**
77       * Match newlines in a platform-independent manner.
78       */
79      public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
80      /**
81       * Match characters which require escaping
82       */
83      public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]");
84  
85      protected static final String DEFAULT_MDCID = "mdc";
86      private static final int TWO_DIGITS = 10;
87      private static final int THREE_DIGITS = 100;
88      private static final int MILLIS_PER_MINUTE = 60000;
89      private static final int MINUTES_PER_HOUR = 60;
90  
91      private static final String COMPONENT_KEY = "RFC5424-Converter";
92  
93      private final Facility facility;
94      private final String defaultId;
95      private final int enterpriseNumber;
96      private final boolean includeMDC;
97      private final String mdcId;
98      private final StructuredDataId mdcSDID;
99      private final String localHostName;
100     private final String appName;
101     private final String messageId;
102     private final String configName;
103     private final String mdcPrefix;
104     private final String eventPrefix;
105     private final List<String> mdcExcludes;
106     private final List<String> mdcIncludes;
107     private final List<String> mdcRequired;
108     private final ListChecker checker;
109     private final ListChecker noopChecker = new NoopChecker();
110     private final boolean includeNewLine;
111     private final String escapeNewLine;
112     private final boolean useTLSMessageFormat;
113 
114     private long lastTimestamp = -1;
115     private String timestamppStr;
116 
117     private final List<PatternFormatter> exceptionFormatters;
118     private final Map<String,  FieldFormatter> fieldFormatters;
119 
120     private RFC5424Layout(final Configuration config, final Facility facility, final String id, final int ein,
121                           final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId,
122                           final String mdcPrefix, final String eventPrefix,
123                           final String appName, final String messageId, final String excludes, final String includes,
124                           final String required, final Charset charset, final String exceptionPattern,
125                           final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) {
126         super(charset);
127         final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class);
128         exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern, false);
129         this.facility = facility;
130         this.defaultId = id == null ? DEFAULT_ID : id;
131         this.enterpriseNumber = ein;
132         this.includeMDC = includeMDC;
133         this.includeNewLine = includeNL;
134         this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
135         this.mdcId = mdcId;
136         this.mdcSDID = new StructuredDataId(mdcId, enterpriseNumber, null, null);
137         this.mdcPrefix = mdcPrefix;
138         this.eventPrefix = eventPrefix;
139         this.appName = appName;
140         this.messageId = messageId;
141         this.useTLSMessageFormat = useTLSMessageFormat;
142         this.localHostName = NetUtils.getLocalHostname();
143         ListChecker c = null;
144         if (excludes != null) {
145             final String[] array = excludes.split(",");
146             if (array.length > 0) {
147                 c = new ExcludeChecker();
148                 mdcExcludes = new ArrayList<String>(array.length);
149                 for (final String str : array) {
150                     mdcExcludes.add(str.trim());
151                 }
152             } else {
153                 mdcExcludes = null;
154             }
155         } else {
156             mdcExcludes = null;
157         }
158         if (includes != null) {
159             final String[] array = includes.split(",");
160             if (array.length > 0) {
161                 c = new IncludeChecker();
162                 mdcIncludes = new ArrayList<String>(array.length);
163                 for (final String str : array) {
164                     mdcIncludes.add(str.trim());
165                 }
166             } else {
167                 mdcIncludes = null;
168             }
169         } else {
170             mdcIncludes = null;
171         }
172         if (required != null) {
173             final String[] array = required.split(",");
174             if (array.length > 0) {
175                 mdcRequired = new ArrayList<String>(array.length);
176                 for (final String str : array) {
177                     mdcRequired.add(str.trim());
178                 }
179             } else {
180                 mdcRequired = null;
181             }
182 
183         } else {
184             mdcRequired = null;
185         }
186         this.checker = c != null ? c : noopChecker;
187         final String name = config == null ? null : config.getName();
188         configName = name != null && name.length() > 0 ? name : null;
189         this.fieldFormatters = createFieldFormatters(loggerFields, config);
190     }
191 
192     private Map<String, FieldFormatter> createFieldFormatters(final LoggerFields[] loggerFields,
193             final Configuration config) {
194         final Map<String, FieldFormatter> sdIdMap = new HashMap<String, FieldFormatter>();
195 
196         if (loggerFields != null) {
197             for (final LoggerFields lField : loggerFields) {
198                 final StructuredDataId key = lField.getSdId() == null ? mdcSDID : lField.getSdId();
199                 final Map<String, List<PatternFormatter>> sdParams = new HashMap<String, List<PatternFormatter>>();
200                 final Map<String, String> fields = lField.getMap();
201                 if (!fields.isEmpty()) {
202                     final PatternParser fieldParser = createPatternParser(config, null);
203 
204                     for (final Map.Entry<String, String> entry : fields.entrySet()) {
205                         final List<PatternFormatter> formatters = fieldParser.parse(entry.getValue(), false);
206                         sdParams.put(entry.getKey(), formatters);
207                     }
208                     final FieldFormatter fieldFormatter = new FieldFormatter(sdParams,
209                             lField.getDiscardIfAllFieldsAreEmpty());
210                     sdIdMap.put(key.toString(), fieldFormatter);
211                 }
212             }
213         }
214         return sdIdMap.size() > 0 ? sdIdMap : null;
215     }
216 
217     /**
218      * Create a PatternParser.
219      *
220      * @param config The Configuration.
221      * @param filterClass Filter the returned plugins after calling the plugin manager.
222      * @return The PatternParser.
223      */
224     private static PatternParser createPatternParser(final Configuration config,
225             final Class<? extends PatternConverter> filterClass) {
226         if (config == null) {
227             return new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class, filterClass);
228         }
229         PatternParser parser = config.getComponent(COMPONENT_KEY);
230         if (parser == null) {
231             parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class);
232             config.addComponent(COMPONENT_KEY, parser);
233             parser = (PatternParser) config.getComponent(COMPONENT_KEY);
234         }
235         return parser;
236     }
237 
238     /**
239      * RFC5424Layout's content format is specified by:<p/>
240      * Key: "structured" Value: "true"<p/>
241      * Key: "format" Value: "RFC5424"<p/>
242      *
243      * @return Map of content format keys supporting RFC5424Layout
244      */
245     @Override
246     public Map<String, String> getContentFormat() {
247         final Map<String, String> result = new HashMap<String, String>();
248         result.put("structured", "true");
249         result.put("formatType", "RFC5424");
250         return result;
251     }
252 
253     /**
254      * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification.
255      *
256      * @param event The LogEvent.
257      * @return The RFC 5424 String representation of the LogEvent.
258      */
259     @Override
260     public String toSerializable(final LogEvent event) {
261         final StringBuilder buf = new StringBuilder();
262         appendPriority(buf, event.getLevel());
263         appendTimestamp(buf, event.getMillis());
264         appendSpace(buf);
265         appendHostName(buf);
266         appendSpace(buf);
267         appendAppName(buf);
268         appendSpace(buf);
269         appendProcessId(buf);
270         appendSpace(buf);
271         appendMessageId(buf, event.getMessage());
272         appendSpace(buf);
273         appendStructuredElements(buf, event);
274         appendMessage(buf, event);
275         if (useTLSMessageFormat) {
276             return new TLSSyslogFrame(buf.toString()).toString();
277         }
278         return buf.toString();
279     }
280 
281     private void appendPriority(final StringBuilder buffer, final Level logLevel) {
282         buffer.append("<");
283         buffer.append(Priority.getPriority(facility, logLevel));
284         buffer.append(">1 ");
285     }
286 
287     private void appendTimestamp(final StringBuilder buffer, final long milliseconds)  {
288         buffer.append(computeTimeStampString(milliseconds));
289     }
290 
291     private void appendSpace(final StringBuilder buffer) {
292         buffer.append(" ");
293     }
294 
295     private void appendHostName(final StringBuilder buffer) {
296         buffer.append(localHostName);
297     }
298 
299     private void appendAppName(final StringBuilder buffer) {
300         if (appName != null) {
301             buffer.append(appName);
302         } else if (configName != null) {
303             buffer.append(configName);
304         } else {
305             buffer.append("-");
306         }
307     }
308 
309     private void appendProcessId(final StringBuilder buffer) {
310         buffer.append(getProcId());
311     }
312 
313     private void appendMessageId(final StringBuilder buffer, final Message message) {
314         final boolean isStructured = message instanceof StructuredDataMessage;
315         final String type = isStructured ? ((StructuredDataMessage) message).getType() : null;
316         if (type != null) {
317             buffer.append(type);
318         } else if (messageId != null) {
319             buffer.append(messageId);
320         } else {
321             buffer.append("-");
322         }
323     }
324 
325     private void appendMessage(final StringBuilder buffer, final LogEvent event) {
326         final Message message = event.getMessage();
327         final String text = message.getFormat();
328 
329         if (text != null && text.length() > 0) {
330             buffer.append(" ").append(escapeNewlines(text, escapeNewLine));
331         }
332 
333         if (exceptionFormatters != null && event.getThrown() != null) {
334             final StringBuilder exception = new StringBuilder(LF);
335             for (final PatternFormatter formatter : exceptionFormatters) {
336                 formatter.format(event, exception);
337             }
338             buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
339         }
340         if (includeNewLine) {
341             buffer.append(LF);
342         }
343     }
344 
345     private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) {
346         final Message message = event.getMessage();
347         final boolean isStructured = message instanceof StructuredDataMessage;
348 
349         if (!isStructured && (fieldFormatters!= null && fieldFormatters.size() == 0) && !includeMDC) {
350             buffer.append("-");
351             return;
352         }
353 
354         final Map<String, StructuredDataElement> sdElements = new HashMap<String, StructuredDataElement>();
355         final Map<String, String> contextMap = event.getContextMap();
356 
357         if (mdcRequired != null) {
358             checkRequired(contextMap);
359         }
360 
361         if (fieldFormatters != null) {
362             for (final Map.Entry<String, FieldFormatter> sdElement: fieldFormatters.entrySet()) {
363                 final String sdId = sdElement.getKey();
364                 final StructuredDataElement elem = sdElement.getValue().format(event);
365                 sdElements.put(sdId, elem);
366             }
367         }
368 
369         if (includeMDC && contextMap.size() > 0) {
370             if (sdElements.containsKey(mdcSDID.toString())) {
371                 final StructuredDataElement union = sdElements.get(mdcSDID.toString());
372                 union.union(contextMap);
373                 sdElements.put(mdcSDID.toString(), union);
374             } else {
375                 final StructuredDataElement formattedContextMap = new StructuredDataElement(contextMap, false);
376                 sdElements.put(mdcSDID.toString(), formattedContextMap);
377             }
378         }
379 
380         if (isStructured) {
381             final StructuredDataMessage data = (StructuredDataMessage) message;
382             final Map<String, String> map = data.getData();
383             final StructuredDataId id = data.getId();
384 
385             if (sdElements.containsKey(id.toString())) {
386                 final StructuredDataElement union = sdElements.get(id.toString());
387                 union.union(map);
388                 sdElements.put(id.toString(), union);
389             } else {
390                 final StructuredDataElement formattedData = new StructuredDataElement(map, false);
391                 sdElements.put(id.toString(), formattedData);
392             }
393         }
394 
395         if (sdElements.size() == 0) {
396             buffer.append("-");
397             return;
398         }
399 
400         for (final Map.Entry<String, StructuredDataElement> entry: sdElements.entrySet()) {
401             formatStructuredElement(entry.getKey(), mdcPrefix, entry.getValue(), buffer, checker);
402         }
403     }
404 
405     private String escapeNewlines(final String text, final String escapeNewLine) {
406         if (null == escapeNewLine) {
407             return text;
408         }
409         return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
410     }
411 
412     protected String getProcId() {
413         return "-";
414     }
415 
416     protected List<String> getMdcExcludes() {
417         return mdcExcludes;
418     }
419 
420     protected List<String> getMdcIncludes() {
421         return mdcIncludes;
422     }
423 
424     private String computeTimeStampString(final long now) {
425         long last;
426         synchronized (this) {
427             last = lastTimestamp;
428             if (now == lastTimestamp) {
429                 return timestamppStr;
430             }
431         }
432 
433         final StringBuilder buffer = new StringBuilder();
434         final Calendar cal = new GregorianCalendar();
435         cal.setTimeInMillis(now);
436         buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
437         buffer.append("-");
438         pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
439         buffer.append("-");
440         pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
441         buffer.append("T");
442         pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
443         buffer.append(":");
444         pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
445         buffer.append(":");
446         pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
447 
448         final int millis = cal.get(Calendar.MILLISECOND);
449         if (millis != 0) {
450             buffer.append('.');
451             pad(millis, THREE_DIGITS, buffer);
452         }
453 
454         int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
455         if (tzmin == 0) {
456             buffer.append("Z");
457         } else {
458             if (tzmin < 0) {
459                 tzmin = -tzmin;
460                 buffer.append("-");
461             } else {
462                 buffer.append("+");
463             }
464             final int tzhour = tzmin / MINUTES_PER_HOUR;
465             tzmin -= tzhour * MINUTES_PER_HOUR;
466             pad(tzhour, TWO_DIGITS, buffer);
467             buffer.append(":");
468             pad(tzmin, TWO_DIGITS, buffer);
469         }
470         synchronized (this) {
471             if (last == lastTimestamp) {
472                 lastTimestamp = now;
473                 timestamppStr = buffer.toString();
474             }
475         }
476         return buffer.toString();
477     }
478 
479     private void pad(final int val, int max, final StringBuilder buf) {
480         while (max > 1) {
481             if (val < max) {
482                 buf.append("0");
483             }
484             max = max / TWO_DIGITS;
485         }
486         buf.append(Integer.toString(val));
487     }
488 
489     private void formatStructuredElement(final String id, final String prefix, final StructuredDataElement data,
490                                          final StringBuilder sb, final ListChecker checker) {
491         if ((id == null && defaultId == null) || data.discard()) {
492             return;
493         }
494 
495         sb.append("[");
496         sb.append(id);
497         if (!mdcSDID.toString().equals(id)) {
498             appendMap(prefix, data.getFields(), sb, noopChecker);
499         } else {
500             appendMap(prefix, data.getFields(), sb, checker);
501         }
502         sb.append("]");
503     }
504 
505     private String getId(final StructuredDataId id) {
506         final StringBuilder sb = new StringBuilder();
507         if (id == null || id.getName() == null) {
508             sb.append(defaultId);
509         } else {
510             sb.append(id.getName());
511         }
512         int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
513         if (ein < 0) {
514             ein = enterpriseNumber;
515         }
516         if (ein >= 0) {
517             sb.append("@").append(ein);
518         }
519         return sb.toString();
520     }
521 
522     private void checkRequired(final Map<String, String> map) {
523         for (final String key : mdcRequired) {
524             final String value = map.get(key);
525             if (value == null) {
526                 throw new LoggingException("Required key " + key + " is missing from the " + mdcId);
527             }
528         }
529     }
530 
531     private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb,
532                            final ListChecker checker) {
533         final SortedMap<String, String> sorted = new TreeMap<String, String>(map);
534         for (final Map.Entry<String, String> entry : sorted.entrySet()) {
535             if (checker.check(entry.getKey()) && entry.getValue() != null) {
536                 sb.append(" ");
537                 if (prefix != null) {
538                     sb.append(prefix);
539                 }
540                 sb.append(escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine)).append("=\"")
541                     .append(escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine)).append("\"");
542             }
543         }
544     }
545 
546     private String escapeSDParams(final String value) {
547         return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
548     }
549 
550     /**
551      * Interface used to check keys in a Map.
552      */
553     private interface ListChecker {
554         boolean check(String key);
555     }
556 
557     /**
558      * Includes only the listed keys.
559      */
560     private class IncludeChecker implements ListChecker {
561         @Override
562         public boolean check(final String key) {
563             return mdcIncludes.contains(key);
564         }
565     }
566 
567     /**
568      * Excludes the listed keys.
569      */
570     private class ExcludeChecker implements ListChecker {
571         @Override
572         public boolean check(final String key) {
573             return !mdcExcludes.contains(key);
574         }
575     }
576 
577     /**
578      * Does nothing.
579      */
580     private class NoopChecker implements ListChecker {
581         @Override
582         public boolean check(final String key) {
583             return true;
584         }
585     }
586 
587     @Override
588     public String toString() {
589         final StringBuilder sb = new StringBuilder();
590         sb.append("facility=").append(facility.name());
591         sb.append(" appName=").append(appName);
592         sb.append(" defaultId=").append(defaultId);
593         sb.append(" enterpriseNumber=").append(enterpriseNumber);
594         sb.append(" newLine=").append(includeNewLine);
595         sb.append(" includeMDC=").append(includeMDC);
596         sb.append(" messageId=").append(messageId);
597         return sb.toString();
598     }
599 
600     /**
601      * Create the RFC 5424 Layout.
602      *
603      * @param facility         The Facility is used to try to classify the message.
604      * @param id               The default structured data id to use when formatting according to RFC 5424.
605      * @param ein              The IANA enterprise number.
606      * @param includeMDC       Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
607      *                         record. Defaults to "true:.
608      * @param mdcId            The id to use for the MDC Structured Data Element.
609      * @param mdcPrefix        The prefix to add to MDC key names.
610      * @param eventPrefix      The prefix to add to event key names.
611      * @param includeNL        If true, a newline will be appended to the end of the syslog record. The default is false.
612      * @param escapeNL         String that should be used to replace newlines within the message text.
613      * @param appName          The value to use as the APP-NAME in the RFC 5424 syslog record.
614      * @param msgId            The default value to be used in the MSGID field of RFC 5424 syslog records.
615      * @param excludes         A comma separated list of MDC keys that should be excluded from the LogEvent.
616      * @param includes         A comma separated list of MDC keys that should be included in the FlumeEvent.
617      * @param required         A comma separated list of MDC keys that must be present in the MDC.
618      * @param exceptionPattern The pattern for formatting exceptions.
619      * @param useTLSMessageFormat If true the message will be formatted according to RFC 5425.
620      * @param loggerFields     Container for the KeyValuePairs containing the patterns
621      * @param config           The Configuration. Some Converters require access to the Interpolator.
622      * @return An RFC5424Layout.
623      */
624     @PluginFactory
625     public static RFC5424Layout createLayout(
626             @PluginAttribute("facility") final String facility,
627             @PluginAttribute("id") final String id,
628             @PluginAttribute("enterpriseNumber") final String ein,
629             @PluginAttribute("includeMDC") final String includeMDC,
630             @PluginAttribute("mdcId") String mdcId,
631             @PluginAttribute("mdcPrefix") final String mdcPrefix,
632             @PluginAttribute("eventPrefix") final String eventPrefix,
633             @PluginAttribute("newLine") final String includeNL,
634             @PluginAttribute("newLineEscape") final String escapeNL,
635             @PluginAttribute("appName") final String appName,
636             @PluginAttribute("messageId") final String msgId,
637             @PluginAttribute("mdcExcludes") final String excludes,
638             @PluginAttribute("mdcIncludes") String includes,
639             @PluginAttribute("mdcRequired") final String required,
640             @PluginAttribute("exceptionPattern") final String exceptionPattern,
641             @PluginAttribute("useTLSMessageFormat") final String useTLSMessageFormat, // RFC 5425
642             @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
643             @PluginConfiguration final Configuration config) {
644         final Charset charset = Charsets.UTF_8;
645         if (includes != null && excludes != null) {
646             LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
647             includes = null;
648         }
649         final Facility f = Facility.toFacility(facility, Facility.LOCAL0);
650         final int enterpriseNumber = Integers.parseInt(ein, DEFAULT_ENTERPRISE_NUMBER);
651         final boolean isMdc = Booleans.parseBoolean(includeMDC, true);
652         final boolean includeNewLine = Boolean.parseBoolean(includeNL);
653         final boolean useTlsMessageFormat = Booleans.parseBoolean(useTLSMessageFormat, false);
654         if (mdcId == null) {
655             mdcId = DEFAULT_MDCID;
656         }
657 
658         return new RFC5424Layout(config, f, id, enterpriseNumber, isMdc, includeNewLine, escapeNL, mdcId, mdcPrefix,
659                 eventPrefix, appName, msgId, excludes, includes, required, charset, exceptionPattern,
660                 useTlsMessageFormat, loggerFields);
661     }
662 
663     private class FieldFormatter {
664 
665         private final Map<String, List<PatternFormatter>> delegateMap;
666         private final boolean discardIfEmpty;
667 
668         public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) {
669             this.discardIfEmpty = discardIfEmpty;
670             this.delegateMap = fieldMap;
671         }
672 
673         public StructuredDataElement format(final LogEvent event) {
674             final Map<String, String> map = new HashMap<String, String>();
675 
676             for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) {
677                 final StringBuilder buffer = new StringBuilder();
678                 for (final PatternFormatter formatter : entry.getValue()) {
679                     formatter.format(event, buffer);
680                 }
681                 map.put(entry.getKey(), buffer.toString());
682             }
683             return new StructuredDataElement(map, discardIfEmpty);
684         }
685     }
686 
687     private class StructuredDataElement {
688 
689         private final Map<String, String> fields;
690         private final boolean discardIfEmpty;
691 
692         public StructuredDataElement(final Map<String, String> fields, final boolean discardIfEmpty) {
693             this.discardIfEmpty = discardIfEmpty;
694             this.fields = fields;
695         }
696 
697         boolean discard() {
698             if (discardIfEmpty == false) {
699                 return false;
700             }
701             boolean foundNotEmptyValue = false;
702             for (final Map.Entry<String, String> entry : fields.entrySet()) {
703                 if (Strings.isNotEmpty(entry.getValue())) {
704                     foundNotEmptyValue = true;
705                     break;
706                 }
707             }
708             return !foundNotEmptyValue;
709         }
710 
711         void union(final Map<String, String> fields) {
712             this.fields.putAll(fields);
713         }
714 
715         Map<String, String> getFields() {
716             return this.fields;
717         }
718     }
719 }