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.MessageCollectionMessage; 055import org.apache.logging.log4j.message.StructuredDataCollectionMessage; 056import org.apache.logging.log4j.message.StructuredDataId; 057import org.apache.logging.log4j.message.StructuredDataMessage; 058import org.apache.logging.log4j.util.ProcessIdUtil; 059import org.apache.logging.log4j.util.StringBuilders; 060import org.apache.logging.log4j.util.Strings; 061 062/** 063 * Formats a log event in accordance with RFC 5424. 064 * 065 * @see <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a> 066 */ 067@Plugin(name = "Rfc5424Layout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) 068public final class Rfc5424Layout extends AbstractStringLayout { 069 070 /** 071 * Not a very good default - it is the Apache Software Foundation's enterprise number. 072 */ 073 public static final int DEFAULT_ENTERPRISE_NUMBER = 18060; 074 /** 075 * The default event id. 076 */ 077 public static final String DEFAULT_ID = "Audit"; 078 /** 079 * Match newlines in a platform-independent manner. 080 */ 081 public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n"); 082 /** 083 * Match characters which require escaping. 084 */ 085 public static final Pattern PARAM_VALUE_ESCAPE_PATTERN = Pattern.compile("[\\\"\\]\\\\]"); 086 087 /** 088 * Default MDC ID: {@value} . 089 */ 090 public static final String DEFAULT_MDCID = "mdc"; 091 092 private static final String LF = "\n"; 093 private static final int TWO_DIGITS = 10; 094 private static final int THREE_DIGITS = 100; 095 private static final int MILLIS_PER_MINUTE = 60000; 096 private static final int MINUTES_PER_HOUR = 60; 097 private static final String COMPONENT_KEY = "RFC5424-Converter"; 098 099 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}