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