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, 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, 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         // This layout formats StructuredDataMessages instead of delegating to the Message itself.
328         final String text = (message instanceof StructuredDataMessage) ? message.getFormat() : message.getFormattedMessage();
329 
330         if (text != null && text.length() > 0) {
331             buffer.append(" ").append(escapeNewlines(text, escapeNewLine));
332         }
333 
334         if (exceptionFormatters != null && event.getThrown() != null) {
335             final StringBuilder exception = new StringBuilder(LF);
336             for (final PatternFormatter formatter : exceptionFormatters) {
337                 formatter.format(event, exception);
338             }
339             buffer.append(escapeNewlines(exception.toString(), escapeNewLine));
340         }
341         if (includeNewLine) {
342             buffer.append(LF);
343         }
344     }
345 
346     private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) {
347         final Message message = event.getMessage();
348         final boolean isStructured = message instanceof StructuredDataMessage;
349 
350         if (!isStructured && (fieldFormatters!= null && fieldFormatters.size() == 0) && !includeMDC) {
351             buffer.append("-");
352             return;
353         }
354 
355         final Map<String, StructuredDataElement> sdElements = new HashMap<String, StructuredDataElement>();
356         final Map<String, String> contextMap = event.getContextMap();
357 
358         if (mdcRequired != null) {
359             checkRequired(contextMap);
360         }
361 
362         if (fieldFormatters != null) {
363             for (final Map.Entry<String, FieldFormatter> sdElement: fieldFormatters.entrySet()) {
364                 final String sdId = sdElement.getKey();
365                 final StructuredDataElement elem = sdElement.getValue().format(event);
366                 sdElements.put(sdId, elem);
367             }
368         }
369 
370         if (includeMDC && contextMap.size() > 0) {
371             if (sdElements.containsKey(mdcSDID.toString())) {
372                 final StructuredDataElement union = sdElements.get(mdcSDID.toString());
373                 union.union(contextMap);
374                 sdElements.put(mdcSDID.toString(), union);
375             } else {
376                 final StructuredDataElement formattedContextMap = new StructuredDataElement(contextMap, false);
377                 sdElements.put(mdcSDID.toString(), formattedContextMap);
378             }
379         }
380 
381         if (isStructured) {
382             final StructuredDataMessage data = (StructuredDataMessage) message;
383             final Map<String, String> map = data.getData();
384             final StructuredDataId id = data.getId();
385             final String sdId = getId(id);
386 
387             if (sdElements.containsKey(sdId)) {
388                 final StructuredDataElement union = sdElements.get(id.toString());
389                 union.union(map);
390                 sdElements.put(sdId, union);
391             } else {
392                 final StructuredDataElement formattedData = new StructuredDataElement(map, false);
393                 sdElements.put(sdId, formattedData);
394             }
395         }
396 
397         if (sdElements.size() == 0) {
398             buffer.append("-");
399             return;
400         }
401 
402         for (final Map.Entry<String, StructuredDataElement> entry: sdElements.entrySet()) {
403             formatStructuredElement(entry.getKey(), mdcPrefix, entry.getValue(), buffer, checker);
404         }
405     }
406 
407     private String escapeNewlines(final String text, final String escapeNewLine) {
408         if (null == escapeNewLine) {
409             return text;
410         }
411         return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
412     }
413 
414     protected String getProcId() {
415         return "-";
416     }
417 
418     protected List<String> getMdcExcludes() {
419         return mdcExcludes;
420     }
421 
422     protected List<String> getMdcIncludes() {
423         return mdcIncludes;
424     }
425 
426     private String computeTimeStampString(final long now) {
427         long last;
428         synchronized (this) {
429             last = lastTimestamp;
430             if (now == lastTimestamp) {
431                 return timestamppStr;
432             }
433         }
434 
435         final StringBuilder buffer = new StringBuilder();
436         final Calendar cal = new GregorianCalendar();
437         cal.setTimeInMillis(now);
438         buffer.append(Integer.toString(cal.get(Calendar.YEAR)));
439         buffer.append("-");
440         pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer);
441         buffer.append("-");
442         pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer);
443         buffer.append("T");
444         pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer);
445         buffer.append(":");
446         pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer);
447         buffer.append(":");
448         pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer);
449 
450         final int millis = cal.get(Calendar.MILLISECOND);
451         if (millis != 0) {
452             buffer.append('.');
453             pad(millis, THREE_DIGITS, buffer);
454         }
455 
456         int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
457         if (tzmin == 0) {
458             buffer.append("Z");
459         } else {
460             if (tzmin < 0) {
461                 tzmin = -tzmin;
462                 buffer.append("-");
463             } else {
464                 buffer.append("+");
465             }
466             final int tzhour = tzmin / MINUTES_PER_HOUR;
467             tzmin -= tzhour * MINUTES_PER_HOUR;
468             pad(tzhour, TWO_DIGITS, buffer);
469             buffer.append(":");
470             pad(tzmin, TWO_DIGITS, buffer);
471         }
472         synchronized (this) {
473             if (last == lastTimestamp) {
474                 lastTimestamp = now;
475                 timestamppStr = buffer.toString();
476             }
477         }
478         return buffer.toString();
479     }
480 
481     private void pad(final int val, int max, final StringBuilder buf) {
482         while (max > 1) {
483             if (val < max) {
484                 buf.append("0");
485             }
486             max = max / TWO_DIGITS;
487         }
488         buf.append(Integer.toString(val));
489     }
490 
491     private void formatStructuredElement(final String id, final String prefix, final StructuredDataElement data,
492                                          final StringBuilder sb, final ListChecker checker) {
493         if ((id == null && defaultId == null) || data.discard()) {
494             return;
495         }
496 
497         sb.append("[");
498         sb.append(id);
499         if (!mdcSDID.toString().equals(id)) {
500             appendMap(prefix, data.getFields(), sb, noopChecker);
501         } else {
502             appendMap(prefix, data.getFields(), sb, checker);
503         }
504         sb.append("]");
505     }
506 
507     private String getId(final StructuredDataId id) {
508         final StringBuilder sb = new StringBuilder();
509         if (id == null || id.getName() == null) {
510             sb.append(defaultId);
511         } else {
512             sb.append(id.getName());
513         }
514         int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber;
515         if (ein < 0) {
516             ein = enterpriseNumber;
517         }
518         if (ein >= 0) {
519             sb.append("@").append(ein);
520         }
521         return sb.toString();
522     }
523 
524     private void checkRequired(final Map<String, String> map) {
525         for (final String key : mdcRequired) {
526             final String value = map.get(key);
527             if (value == null) {
528                 throw new LoggingException("Required key " + key + " is missing from the " + mdcId);
529             }
530         }
531     }
532 
533     private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb,
534                            final ListChecker checker) {
535         final SortedMap<String, String> sorted = new TreeMap<String, String>(map);
536         for (final Map.Entry<String, String> entry : sorted.entrySet()) {
537             if (checker.check(entry.getKey()) && entry.getValue() != null) {
538                 sb.append(" ");
539                 if (prefix != null) {
540                     sb.append(prefix);
541                 }
542                 sb.append(escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine)).append("=\"")
543                     .append(escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine)).append("\"");
544             }
545         }
546     }
547 
548     private String escapeSDParams(final String value) {
549         return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
550     }
551 
552     /**
553      * Interface used to check keys in a Map.
554      */
555     private interface ListChecker {
556         boolean check(String key);
557     }
558 
559     /**
560      * Includes only the listed keys.
561      */
562     private class IncludeChecker implements ListChecker {
563         @Override
564         public boolean check(final String key) {
565             return mdcIncludes.contains(key);
566         }
567     }
568 
569     /**
570      * Excludes the listed keys.
571      */
572     private class ExcludeChecker implements ListChecker {
573         @Override
574         public boolean check(final String key) {
575             return !mdcExcludes.contains(key);
576         }
577     }
578 
579     /**
580      * Does nothing.
581      */
582     private class NoopChecker implements ListChecker {
583         @Override
584         public boolean check(final String key) {
585             return true;
586         }
587     }
588 
589     @Override
590     public String toString() {
591         final StringBuilder sb = new StringBuilder();
592         sb.append("facility=").append(facility.name());
593         sb.append(" appName=").append(appName);
594         sb.append(" defaultId=").append(defaultId);
595         sb.append(" enterpriseNumber=").append(enterpriseNumber);
596         sb.append(" newLine=").append(includeNewLine);
597         sb.append(" includeMDC=").append(includeMDC);
598         sb.append(" messageId=").append(messageId);
599         return sb.toString();
600     }
601 
602     /**
603      * Create the RFC 5424 Layout.
604      *
605      * @param facility         The Facility is used to try to classify the message.
606      * @param id               The default structured data id to use when formatting according to RFC 5424.
607      * @param ein              The IANA enterprise number.
608      * @param includeMDC       Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
609      *                         record. Defaults to "true:.
610      * @param mdcId            The id to use for the MDC Structured Data Element.
611      * @param mdcPrefix        The prefix to add to MDC key names.
612      * @param eventPrefix      The prefix to add to event key names.
613      * @param includeNL        If true, a newline will be appended to the end of the syslog record. The default is false.
614      * @param escapeNL         String that should be used to replace newlines within the message text.
615      * @param appName          The value to use as the APP-NAME in the RFC 5424 syslog record.
616      * @param msgId            The default value to be used in the MSGID field of RFC 5424 syslog records.
617      * @param excludes         A comma separated list of MDC keys that should be excluded from the LogEvent.
618      * @param includes         A comma separated list of MDC keys that should be included in the FlumeEvent.
619      * @param required         A comma separated list of MDC keys that must be present in the MDC.
620      * @param exceptionPattern The pattern for formatting exceptions.
621      * @param useTLSMessageFormat If true the message will be formatted according to RFC 5425.
622      * @param loggerFields     Container for the KeyValuePairs containing the patterns
623      * @param config           The Configuration. Some Converters require access to the Interpolator.
624      * @return An RFC5424Layout.
625      */
626     @PluginFactory
627     public static RFC5424Layout createLayout(
628             @PluginAttribute("facility") final String facility,
629             @PluginAttribute("id") final String id,
630             @PluginAttribute("enterpriseNumber") final String ein,
631             @PluginAttribute("includeMDC") final String includeMDC,
632             @PluginAttribute("mdcId") String mdcId,
633             @PluginAttribute("mdcPrefix") final String mdcPrefix,
634             @PluginAttribute("eventPrefix") final String eventPrefix,
635             @PluginAttribute("newLine") final String includeNL,
636             @PluginAttribute("newLineEscape") final String escapeNL,
637             @PluginAttribute("appName") final String appName,
638             @PluginAttribute("messageId") final String msgId,
639             @PluginAttribute("mdcExcludes") final String excludes,
640             @PluginAttribute("mdcIncludes") String includes,
641             @PluginAttribute("mdcRequired") final String required,
642             @PluginAttribute("exceptionPattern") final String exceptionPattern,
643             @PluginAttribute("useTLSMessageFormat") final String useTLSMessageFormat, // RFC 5425
644             @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
645             @PluginConfiguration final Configuration config) {
646         final Charset charset = Charsets.UTF_8;
647         if (includes != null && excludes != null) {
648             LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
649             includes = null;
650         }
651         final Facility f = Facility.toFacility(facility, Facility.LOCAL0);
652         final int enterpriseNumber = Integers.parseInt(ein, DEFAULT_ENTERPRISE_NUMBER);
653         final boolean isMdc = Booleans.parseBoolean(includeMDC, true);
654         final boolean includeNewLine = Boolean.parseBoolean(includeNL);
655         final boolean useTlsMessageFormat = Booleans.parseBoolean(useTLSMessageFormat, false);
656         if (mdcId == null) {
657             mdcId = DEFAULT_MDCID;
658         }
659 
660         return new RFC5424Layout(config, f, id, enterpriseNumber, isMdc, includeNewLine, escapeNL, mdcId, mdcPrefix,
661                 eventPrefix, appName, msgId, excludes, includes, required, charset, exceptionPattern,
662                 useTlsMessageFormat, loggerFields);
663     }
664 
665     private class FieldFormatter {
666 
667         private final Map<String, List<PatternFormatter>> delegateMap;
668         private final boolean discardIfEmpty;
669 
670         public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) {
671             this.discardIfEmpty = discardIfEmpty;
672             this.delegateMap = fieldMap;
673         }
674 
675         public StructuredDataElement format(final LogEvent event) {
676             final Map<String, String> map = new HashMap<String, String>();
677 
678             for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) {
679                 final StringBuilder buffer = new StringBuilder();
680                 for (final PatternFormatter formatter : entry.getValue()) {
681                     formatter.format(event, buffer);
682                 }
683                 map.put(entry.getKey(), buffer.toString());
684             }
685             return new StructuredDataElement(map, discardIfEmpty);
686         }
687     }
688 
689     private class StructuredDataElement {
690 
691         private final Map<String, String> fields;
692         private final boolean discardIfEmpty;
693 
694         public StructuredDataElement(final Map<String, String> fields, final boolean discardIfEmpty) {
695             this.discardIfEmpty = discardIfEmpty;
696             this.fields = fields;
697         }
698 
699         boolean discard() {
700             if (discardIfEmpty == false) {
701                 return false;
702             }
703             boolean foundNotEmptyValue = false;
704             for (final Map.Entry<String, String> entry : fields.entrySet()) {
705                 if (Strings.isNotEmpty(entry.getValue())) {
706                     foundNotEmptyValue = true;
707                     break;
708                 }
709             }
710             return !foundNotEmptyValue;
711         }
712 
713         void union(final Map<String, String> fields) {
714             this.fields.putAll(fields);
715         }
716 
717         Map<String, String> getFields() {
718             return this.fields;
719         }
720     }
721 }