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