001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.layout; 018 019import java.nio.charset.Charset; 020import java.nio.charset.StandardCharsets; 021import java.util.ArrayList; 022import java.util.Calendar; 023import java.util.GregorianCalendar; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.SortedMap; 028import java.util.TreeMap; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031 032import org.apache.logging.log4j.Level; 033import org.apache.logging.log4j.LoggingException; 034import org.apache.logging.log4j.core.Layout; 035import org.apache.logging.log4j.core.LogEvent; 036import org.apache.logging.log4j.core.appender.TlsSyslogFrame; 037import org.apache.logging.log4j.core.config.Configuration; 038import org.apache.logging.log4j.core.config.Node; 039import org.apache.logging.log4j.core.config.plugins.Plugin; 040import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 041import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 042import org.apache.logging.log4j.core.config.plugins.PluginElement; 043import org.apache.logging.log4j.core.config.plugins.PluginFactory; 044import org.apache.logging.log4j.core.net.Facility; 045import org.apache.logging.log4j.core.net.Priority; 046import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; 047import org.apache.logging.log4j.core.pattern.PatternConverter; 048import org.apache.logging.log4j.core.pattern.PatternFormatter; 049import org.apache.logging.log4j.core.pattern.PatternParser; 050import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter; 051import org.apache.logging.log4j.core.util.NetUtils; 052import org.apache.logging.log4j.core.util.Patterns; 053import org.apache.logging.log4j.message.Message; 054import org.apache.logging.log4j.message.StructuredDataId; 055import org.apache.logging.log4j.message.StructuredDataMessage; 056import org.apache.logging.log4j.util.StringBuilders; 057import org.apache.logging.log4j.util.Strings; 058 059 060/** 061 * Formats a log event in accordance with RFC 5424. 062 * 063 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a> 064 */ 065@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) 066public final class Rfc5424Layout extends AbstractStringLayout { 067 068 private static final long serialVersionUID = 1L; 069 070 private static final String LF = "\n"; 071 072 /** 073 * Not a very good default - it is the Apache Software Foundation's enterprise number. 074 */ 075 public static final int DEFAULT_ENTERPRISE_NUMBER = 18060; 076 /** 077 * The default event id. 078 */ 079 public static final String DEFAULT_ID = "Audit"; 080 /** 081 * Match newlines in a platform-independent manner. 082 */ 083 public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n"); 084 /** 085 * Match characters which require escaping 086 */ 087 public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]"); 088 089 public static final String DEFAULT_MDCID = "mdc"; 090 private static final int TWO_DIGITS = 10; 091 private static final int THREE_DIGITS = 100; 092 private static final int MILLIS_PER_MINUTE = 60000; 093 private static final int MINUTES_PER_HOUR = 60; 094 095 private static final String COMPONENT_KEY = "RFC5424-Converter"; 096 097 private final Facility facility; 098 private final String defaultId; 099 private final int enterpriseNumber; 100 private final boolean includeMdc; 101 private final String mdcId; 102 private final StructuredDataId mdcSdId; 103 private final String localHostName; 104 private final String appName; 105 private final String messageId; 106 private final String configName; 107 private final String mdcPrefix; 108 private final String eventPrefix; 109 private final List<String> mdcExcludes; 110 private final List<String> mdcIncludes; 111 private final List<String> mdcRequired; 112 private final ListChecker checker; 113 private final ListChecker noopChecker = new NoopChecker(); 114 private final boolean includeNewLine; 115 private final String escapeNewLine; 116 private final boolean useTlsMessageFormat; 117 118 private long lastTimestamp = -1; 119 private String timestamppStr; 120 121 private final List<PatternFormatter> exceptionFormatters; 122 private final Map<String, FieldFormatter> fieldFormatters; 123 124 private Rfc5424Layout(final Configuration config, final Facility facility, final String id, final int ein, 125 final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId, 126 final String mdcPrefix, final String eventPrefix, 127 final String appName, final String messageId, final String excludes, final String includes, 128 final String required, final Charset charset, final String exceptionPattern, 129 final boolean useTLSMessageFormat, final LoggerFields[] loggerFields) { 130 super(charset); 131 final PatternParser exceptionParser = createPatternParser(config, ThrowablePatternConverter.class); 132 exceptionFormatters = exceptionPattern == null ? null : exceptionParser.parse(exceptionPattern, false, false); 133 this.facility = facility; 134 this.defaultId = id == null ? DEFAULT_ID : id; 135 this.enterpriseNumber = ein; 136 this.includeMdc = includeMDC; 137 this.includeNewLine = includeNL; 138 this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL); 139 this.mdcId = mdcId; 140 this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, null); 141 this.mdcPrefix = mdcPrefix; 142 this.eventPrefix = eventPrefix; 143 this.appName = appName; 144 this.messageId = messageId; 145 this.useTlsMessageFormat = useTLSMessageFormat; 146 this.localHostName = NetUtils.getLocalHostname(); 147 ListChecker c = null; 148 if (excludes != null) { 149 final String[] array = excludes.split(Patterns.COMMA_SEPARATOR); 150 if (array.length > 0) { 151 c = new ExcludeChecker(); 152 mdcExcludes = new ArrayList<>(array.length); 153 for (final String str : array) { 154 mdcExcludes.add(str.trim()); 155 } 156 } else { 157 mdcExcludes = null; 158 } 159 } else { 160 mdcExcludes = null; 161 } 162 if (includes != null) { 163 final String[] array = includes.split(Patterns.COMMA_SEPARATOR); 164 if (array.length > 0) { 165 c = new IncludeChecker(); 166 mdcIncludes = new ArrayList<>(array.length); 167 for (final String str : array) { 168 mdcIncludes.add(str.trim()); 169 } 170 } else { 171 mdcIncludes = null; 172 } 173 } else { 174 mdcIncludes = null; 175 } 176 if (required != null) { 177 final String[] array = required.split(Patterns.COMMA_SEPARATOR); 178 if (array.length > 0) { 179 mdcRequired = new ArrayList<>(array.length); 180 for (final String str : array) { 181 mdcRequired.add(str.trim()); 182 } 183 } else { 184 mdcRequired = null; 185 } 186 187 } else { 188 mdcRequired = null; 189 } 190 this.checker = c != null ? c : noopChecker; 191 final String name = config == null ? null : config.getName(); 192 configName = Strings.isNotEmpty(name) ? name : null; 193 this.fieldFormatters = createFieldFormatters(loggerFields, config); 194 } 195 196 private Map<String, FieldFormatter> createFieldFormatters(final LoggerFields[] loggerFields, 197 final Configuration config) { 198 final Map<String, FieldFormatter> sdIdMap = new HashMap<>(); 199 200 if (loggerFields != null) { 201 for (final LoggerFields lField : loggerFields) { 202 final StructuredDataId key = lField.getSdId() == null ? mdcSdId : lField.getSdId(); 203 final Map<String, List<PatternFormatter>> sdParams = new HashMap<>(); 204 final Map<String, String> fields = lField.getMap(); 205 if (!fields.isEmpty()) { 206 final PatternParser fieldParser = createPatternParser(config, null); 207 208 for (final Map.Entry<String, String> entry : fields.entrySet()) { 209 final List<PatternFormatter> formatters = fieldParser.parse(entry.getValue(), false, false); 210 sdParams.put(entry.getKey(), formatters); 211 } 212 final FieldFormatter fieldFormatter = new FieldFormatter(sdParams, 213 lField.getDiscardIfAllFieldsAreEmpty()); 214 sdIdMap.put(key.toString(), fieldFormatter); 215 } 216 } 217 } 218 return sdIdMap.size() > 0 ? sdIdMap : null; 219 } 220 221 /** 222 * Create a PatternParser. 223 * 224 * @param config The Configuration. 225 * @param filterClass Filter the returned plugins after calling the plugin manager. 226 * @return The PatternParser. 227 */ 228 private static PatternParser createPatternParser(final Configuration config, 229 final Class<? extends PatternConverter> filterClass) { 230 if (config == null) { 231 return new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class, filterClass); 232 } 233 PatternParser parser = config.getComponent(COMPONENT_KEY); 234 if (parser == null) { 235 parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class); 236 config.addComponent(COMPONENT_KEY, parser); 237 parser = (PatternParser) config.getComponent(COMPONENT_KEY); 238 } 239 return parser; 240 } 241 242 /** 243 * Gets this Rfc5424Layout's content format. Specified by: 244 * <ul> 245 * <li>Key: "structured" Value: "true"</li> 246 * <li>Key: "format" Value: "RFC5424"</li> 247 * </ul> 248 * 249 * @return Map of content format keys supporting Rfc5424Layout 250 */ 251 @Override 252 public Map<String, String> getContentFormat() { 253 final Map<String, String> result = new HashMap<>(); 254 result.put("structured", "true"); 255 result.put("formatType", "RFC5424"); 256 return result; 257 } 258 259 /** 260 * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification. 261 * 262 * @param event The LogEvent. 263 * @return The RFC 5424 String representation of the LogEvent. 264 */ 265 @Override 266 public String toSerializable(final LogEvent event) { 267 final StringBuilder buf = new StringBuilder(); 268 appendPriority(buf, event.getLevel()); 269 appendTimestamp(buf, event.getTimeMillis()); 270 appendSpace(buf); 271 appendHostName(buf); 272 appendSpace(buf); 273 appendAppName(buf); 274 appendSpace(buf); 275 appendProcessId(buf); 276 appendSpace(buf); 277 appendMessageId(buf, event.getMessage()); 278 appendSpace(buf); 279 appendStructuredElements(buf, event); 280 appendMessage(buf, event); 281 if (useTlsMessageFormat) { 282 return new TlsSyslogFrame(buf.toString()).toString(); 283 } 284 return buf.toString(); 285 } 286 287 private void appendPriority(final StringBuilder buffer, final Level logLevel) { 288 buffer.append('<'); 289 buffer.append(Priority.getPriority(facility, logLevel)); 290 buffer.append(">1 "); 291 } 292 293 private void appendTimestamp(final StringBuilder buffer, final long milliseconds) { 294 buffer.append(computeTimeStampString(milliseconds)); 295 } 296 297 private void appendSpace(final StringBuilder buffer) { 298 buffer.append(' '); 299 } 300 301 private void appendHostName(final StringBuilder buffer) { 302 buffer.append(localHostName); 303 } 304 305 private void appendAppName(final StringBuilder buffer) { 306 if (appName != null) { 307 buffer.append(appName); 308 } else if (configName != null) { 309 buffer.append(configName); 310 } else { 311 buffer.append('-'); 312 } 313 } 314 315 private void appendProcessId(final StringBuilder buffer) { 316 buffer.append(getProcId()); 317 } 318 319 private void appendMessageId(final StringBuilder buffer, final Message message) { 320 final boolean isStructured = message instanceof StructuredDataMessage; 321 final String type = isStructured ? ((StructuredDataMessage) message).getType() : null; 322 if (type != null) { 323 buffer.append(type); 324 } else if (messageId != null) { 325 buffer.append(messageId); 326 } else { 327 buffer.append('-'); 328 } 329 } 330 331 private void appendMessage(final StringBuilder buffer, final LogEvent event) { 332 final Message message = event.getMessage(); 333 // This layout formats StructuredDataMessages instead of delegating to the Message itself. 334 final String text = (message instanceof StructuredDataMessage) ? message.getFormat() : message.getFormattedMessage(); 335 336 if (text != null && text.length() > 0) { 337 buffer.append(' ').append(escapeNewlines(text, escapeNewLine)); 338 } 339 340 if (exceptionFormatters != null && event.getThrown() != null) { 341 final StringBuilder exception = new StringBuilder(LF); 342 for (final PatternFormatter formatter : exceptionFormatters) { 343 formatter.format(event, exception); 344 } 345 buffer.append(escapeNewlines(exception.toString(), escapeNewLine)); 346 } 347 if (includeNewLine) { 348 buffer.append(LF); 349 } 350 } 351 352 private void appendStructuredElements(final StringBuilder buffer, final LogEvent event) { 353 final Message message = event.getMessage(); 354 final boolean isStructured = message instanceof StructuredDataMessage; 355 356 if (!isStructured && (fieldFormatters!= null && fieldFormatters.isEmpty()) && !includeMdc) { 357 buffer.append('-'); 358 return; 359 } 360 361 final Map<String, StructuredDataElement> sdElements = new HashMap<>(); 362 final Map<String, String> contextMap = event.getContextMap(); 363 364 if (mdcRequired != null) { 365 checkRequired(contextMap); 366 } 367 368 if (fieldFormatters != null) { 369 for (final Map.Entry<String, FieldFormatter> sdElement: fieldFormatters.entrySet()) { 370 final String sdId = sdElement.getKey(); 371 final StructuredDataElement elem = sdElement.getValue().format(event); 372 sdElements.put(sdId, elem); 373 } 374 } 375 376 if (includeMdc && contextMap.size() > 0) { 377 final String mdcSdIdStr = mdcSdId.toString(); 378 final StructuredDataElement union = sdElements.get(mdcSdIdStr); 379 if (union != null) { 380 union.union(contextMap); 381 sdElements.put(mdcSdIdStr, union); 382 } else { 383 final StructuredDataElement formattedContextMap = new StructuredDataElement(contextMap, false); 384 sdElements.put(mdcSdIdStr, formattedContextMap); 385 } 386 } 387 388 if (isStructured) { 389 final StructuredDataMessage data = (StructuredDataMessage) message; 390 final Map<String, String> map = data.getData(); 391 final StructuredDataId id = data.getId(); 392 final String sdId = getId(id); 393 394 if (sdElements.containsKey(sdId)) { 395 final StructuredDataElement union = sdElements.get(id.toString()); 396 union.union(map); 397 sdElements.put(sdId, union); 398 } else { 399 final StructuredDataElement formattedData = new StructuredDataElement(map, false); 400 sdElements.put(sdId, formattedData); 401 } 402 } 403 404 if (sdElements.isEmpty()) { 405 buffer.append('-'); 406 return; 407 } 408 409 for (final Map.Entry<String, StructuredDataElement> entry: sdElements.entrySet()) { 410 formatStructuredElement(entry.getKey(), mdcPrefix, entry.getValue(), buffer, checker); 411 } 412 } 413 414 private String escapeNewlines(final String text, final String escapeNewLine) { 415 if (null == escapeNewLine) { 416 return text; 417 } 418 return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine); 419 } 420 421 protected String getProcId() { 422 return "-"; 423 } 424 425 protected List<String> getMdcExcludes() { 426 return mdcExcludes; 427 } 428 429 protected List<String> getMdcIncludes() { 430 return mdcIncludes; 431 } 432 433 private String computeTimeStampString(final long now) { 434 long last; 435 synchronized (this) { 436 last = lastTimestamp; 437 if (now == lastTimestamp) { 438 return timestamppStr; 439 } 440 } 441 442 final StringBuilder buffer = new StringBuilder(); 443 final Calendar cal = new GregorianCalendar(); 444 cal.setTimeInMillis(now); 445 buffer.append(Integer.toString(cal.get(Calendar.YEAR))); 446 buffer.append('-'); 447 pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buffer); 448 buffer.append('-'); 449 pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buffer); 450 buffer.append('T'); 451 pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buffer); 452 buffer.append(':'); 453 pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buffer); 454 buffer.append(':'); 455 pad(cal.get(Calendar.SECOND), TWO_DIGITS, buffer); 456 buffer.append('.'); 457 pad(cal.get(Calendar.MILLISECOND), THREE_DIGITS, buffer); 458 459 int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE; 460 if (tzmin == 0) { 461 buffer.append('Z'); 462 } else { 463 if (tzmin < 0) { 464 tzmin = -tzmin; 465 buffer.append('-'); 466 } else { 467 buffer.append('+'); 468 } 469 final int tzhour = tzmin / MINUTES_PER_HOUR; 470 tzmin -= tzhour * MINUTES_PER_HOUR; 471 pad(tzhour, TWO_DIGITS, buffer); 472 buffer.append(':'); 473 pad(tzmin, TWO_DIGITS, buffer); 474 } 475 synchronized (this) { 476 if (last == lastTimestamp) { 477 lastTimestamp = now; 478 timestamppStr = buffer.toString(); 479 } 480 } 481 return buffer.toString(); 482 } 483 484 private void pad(final int val, int max, final StringBuilder buf) { 485 while (max > 1) { 486 if (val < max) { 487 buf.append('0'); 488 } 489 max = max / TWO_DIGITS; 490 } 491 buf.append(Integer.toString(val)); 492 } 493 494 private void formatStructuredElement(final String id, final String prefix, final StructuredDataElement data, 495 final StringBuilder sb, final ListChecker checker) { 496 if ((id == null && defaultId == null) || data.discard()) { 497 return; 498 } 499 500 sb.append('['); 501 sb.append(id); 502 if (!mdcSdId.toString().equals(id)) { 503 appendMap(prefix, data.getFields(), sb, noopChecker); 504 } else { 505 appendMap(prefix, data.getFields(), sb, checker); 506 } 507 sb.append(']'); 508 } 509 510 private String getId(final StructuredDataId id) { 511 final StringBuilder sb = new StringBuilder(); 512 if (id == null || id.getName() == null) { 513 sb.append(defaultId); 514 } else { 515 sb.append(id.getName()); 516 } 517 int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber; 518 if (ein < 0) { 519 ein = enterpriseNumber; 520 } 521 if (ein >= 0) { 522 sb.append('@').append(ein); 523 } 524 return sb.toString(); 525 } 526 527 private void checkRequired(final Map<String, String> map) { 528 for (final String key : mdcRequired) { 529 final String value = map.get(key); 530 if (value == null) { 531 throw new LoggingException("Required key " + key + " is missing from the " + mdcId); 532 } 533 } 534 } 535 536 private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb, 537 final ListChecker checker) { 538 final SortedMap<String, String> sorted = new TreeMap<>(map); 539 for (final Map.Entry<String, String> entry : sorted.entrySet()) { 540 if (checker.check(entry.getKey()) && entry.getValue() != null) { 541 sb.append(' '); 542 if (prefix != null) { 543 sb.append(prefix); 544 } 545 final String safeKey = escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine); 546 final String safeValue = escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine); 547 StringBuilders.appendKeyDqValue(sb, safeKey, safeValue); 548 } 549 } 550 } 551 552 private String escapeSDParams(final String value) { 553 return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0"); 554 } 555 556 /** 557 * Interface used to check keys in a Map. 558 */ 559 private interface ListChecker { 560 boolean check(String key); 561 } 562 563 /** 564 * Includes only the listed keys. 565 */ 566 private class IncludeChecker implements ListChecker { 567 @Override 568 public boolean check(final String key) { 569 return mdcIncludes.contains(key); 570 } 571 } 572 573 /** 574 * Excludes the listed keys. 575 */ 576 private class ExcludeChecker implements ListChecker { 577 @Override 578 public boolean check(final String key) { 579 return !mdcExcludes.contains(key); 580 } 581 } 582 583 /** 584 * Does nothing. 585 */ 586 private class NoopChecker implements ListChecker { 587 @Override 588 public boolean check(final String key) { 589 return true; 590 } 591 } 592 593 @Override 594 public String toString() { 595 final StringBuilder sb = new StringBuilder(); 596 sb.append("facility=").append(facility.name()); 597 sb.append(" appName=").append(appName); 598 sb.append(" defaultId=").append(defaultId); 599 sb.append(" enterpriseNumber=").append(enterpriseNumber); 600 sb.append(" newLine=").append(includeNewLine); 601 sb.append(" includeMDC=").append(includeMdc); 602 sb.append(" messageId=").append(messageId); 603 return sb.toString(); 604 } 605 606 /** 607 * Create the RFC 5424 Layout. 608 * 609 * @param facility The Facility is used to try to classify the message. 610 * @param id The default structured data id to use when formatting according to RFC 5424. 611 * @param enterpriseNumber The IANA enterprise number. 612 * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog 613 * record. Defaults to "true:. 614 * @param mdcId The id to use for the MDC Structured Data Element. 615 * @param mdcPrefix The prefix to add to MDC key names. 616 * @param eventPrefix The prefix to add to event key names. 617 * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. 618 * @param escapeNL String that should be used to replace newlines within the message text. 619 * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. 620 * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. 621 * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent. 622 * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent. 623 * @param required A comma separated list of MDC keys that must be present in the MDC. 624 * @param exceptionPattern The pattern for formatting exceptions. 625 * @param useTlsMessageFormat If true the message will be formatted according to RFC 5425. 626 * @param loggerFields Container for the KeyValuePairs containing the patterns 627 * @param config The Configuration. Some Converters require access to the Interpolator. 628 * @return An Rfc5424Layout. 629 */ 630 @PluginFactory 631 public static Rfc5424Layout createLayout( 632 @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility, 633 @PluginAttribute("id") final String id, 634 @PluginAttribute(value = "enterpriseNumber", defaultInt = DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber, 635 @PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC, 636 @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) final String mdcId, 637 @PluginAttribute("mdcPrefix") final String mdcPrefix, 638 @PluginAttribute("eventPrefix") final String eventPrefix, 639 @PluginAttribute(value = "newLine", defaultBoolean = false) final boolean newLine, 640 @PluginAttribute("newLineEscape") final String escapeNL, 641 @PluginAttribute("appName") final String appName, 642 @PluginAttribute("messageId") final String msgId, 643 @PluginAttribute("mdcExcludes") final String excludes, 644 @PluginAttribute("mdcIncludes") String includes, 645 @PluginAttribute("mdcRequired") final String required, 646 @PluginAttribute("exceptionPattern") final String exceptionPattern, 647 @PluginAttribute(value = "useTlsMessageFormat", defaultBoolean = false) final boolean useTlsMessageFormat, // RFC 5425 648 @PluginElement("LoggerFields") final LoggerFields[] loggerFields, 649 @PluginConfiguration final Configuration config) { 650 if (includes != null && excludes != null) { 651 LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored"); 652 includes = null; 653 } 654 655 return new Rfc5424Layout(config, facility, id, enterpriseNumber, includeMDC, newLine, escapeNL, mdcId, mdcPrefix, 656 eventPrefix, appName, msgId, excludes, includes, required, StandardCharsets.UTF_8, exceptionPattern, 657 useTlsMessageFormat, loggerFields); 658 } 659 660 private class FieldFormatter { 661 662 private final Map<String, List<PatternFormatter>> delegateMap; 663 private final boolean discardIfEmpty; 664 665 public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) { 666 this.discardIfEmpty = discardIfEmpty; 667 this.delegateMap = fieldMap; 668 } 669 670 public StructuredDataElement format(final LogEvent event) { 671 final Map<String, String> map = new HashMap<>(); 672 673 for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) { 674 final StringBuilder buffer = new StringBuilder(); 675 for (final PatternFormatter formatter : entry.getValue()) { 676 formatter.format(event, buffer); 677 } 678 map.put(entry.getKey(), buffer.toString()); 679 } 680 return new StructuredDataElement(map, discardIfEmpty); 681 } 682 } 683 684 private class StructuredDataElement { 685 686 private final Map<String, String> fields; 687 private final boolean discardIfEmpty; 688 689 public StructuredDataElement(final Map<String, String> fields, final boolean discardIfEmpty) { 690 this.discardIfEmpty = discardIfEmpty; 691 this.fields = fields; 692 } 693 694 boolean discard() { 695 if (discardIfEmpty == false) { 696 return false; 697 } 698 boolean foundNotEmptyValue = false; 699 for (final Map.Entry<String, String> entry : fields.entrySet()) { 700 if (Strings.isNotEmpty(entry.getValue())) { 701 foundNotEmptyValue = true; 702 break; 703 } 704 } 705 return !foundNotEmptyValue; 706 } 707 708 void union(final Map<String, String> fields) { 709 this.fields.putAll(fields); 710 } 711 712 Map<String, String> getFields() { 713 return this.fields; 714 } 715 } 716}