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 buffer.append('.'); 455 pad(cal.get(Calendar.MILLISECOND), THREE_DIGITS, buffer); 456 457 int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE; 458 if (tzmin == 0) { 459 buffer.append('Z'); 460 } else { 461 if (tzmin < 0) { 462 tzmin = -tzmin; 463 buffer.append('-'); 464 } else { 465 buffer.append('+'); 466 } 467 final int tzhour = tzmin / MINUTES_PER_HOUR; 468 tzmin -= tzhour * MINUTES_PER_HOUR; 469 pad(tzhour, TWO_DIGITS, buffer); 470 buffer.append(':'); 471 pad(tzmin, TWO_DIGITS, buffer); 472 } 473 synchronized (this) { 474 if (last == lastTimestamp) { 475 lastTimestamp = now; 476 timestamppStr = buffer.toString(); 477 } 478 } 479 return buffer.toString(); 480 } 481 482 private void pad(final int val, int max, final StringBuilder buf) { 483 while (max > 1) { 484 if (val < max) { 485 buf.append('0'); 486 } 487 max = max / TWO_DIGITS; 488 } 489 buf.append(Integer.toString(val)); 490 } 491 492 private void formatStructuredElement(final String id, final String prefix, final StructuredDataElement data, 493 final StringBuilder sb, final ListChecker checker) { 494 if ((id == null && defaultId == null) || data.discard()) { 495 return; 496 } 497 498 sb.append('['); 499 sb.append(id); 500 if (!mdcSdId.toString().equals(id)) { 501 appendMap(prefix, data.getFields(), sb, noopChecker); 502 } else { 503 appendMap(prefix, data.getFields(), sb, checker); 504 } 505 sb.append(']'); 506 } 507 508 private String getId(final StructuredDataId id) { 509 final StringBuilder sb = new StringBuilder(); 510 if (id == null || id.getName() == null) { 511 sb.append(defaultId); 512 } else { 513 sb.append(id.getName()); 514 } 515 int ein = id != null ? id.getEnterpriseNumber() : enterpriseNumber; 516 if (ein < 0) { 517 ein = enterpriseNumber; 518 } 519 if (ein >= 0) { 520 sb.append('@').append(ein); 521 } 522 return sb.toString(); 523 } 524 525 private void checkRequired(final Map<String, String> map) { 526 for (final String key : mdcRequired) { 527 final String value = map.get(key); 528 if (value == null) { 529 throw new LoggingException("Required key " + key + " is missing from the " + mdcId); 530 } 531 } 532 } 533 534 private void appendMap(final String prefix, final Map<String, String> map, final StringBuilder sb, 535 final ListChecker checker) { 536 final SortedMap<String, String> sorted = new TreeMap<String, String>(map); 537 for (final Map.Entry<String, String> entry : sorted.entrySet()) { 538 if (checker.check(entry.getKey()) && entry.getValue() != null) { 539 sb.append(' '); 540 if (prefix != null) { 541 sb.append(prefix); 542 } 543 sb.append(escapeNewlines(escapeSDParams(entry.getKey()), escapeNewLine)).append("=\"") 544 .append(escapeNewlines(escapeSDParams(entry.getValue()), escapeNewLine)).append('"'); 545 } 546 } 547 } 548 549 private String escapeSDParams(final String value) { 550 return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0"); 551 } 552 553 /** 554 * Interface used to check keys in a Map. 555 */ 556 private interface ListChecker { 557 boolean check(String key); 558 } 559 560 /** 561 * Includes only the listed keys. 562 */ 563 private class IncludeChecker implements ListChecker { 564 @Override 565 public boolean check(final String key) { 566 return mdcIncludes.contains(key); 567 } 568 } 569 570 /** 571 * Excludes the listed keys. 572 */ 573 private class ExcludeChecker implements ListChecker { 574 @Override 575 public boolean check(final String key) { 576 return !mdcExcludes.contains(key); 577 } 578 } 579 580 /** 581 * Does nothing. 582 */ 583 private class NoopChecker implements ListChecker { 584 @Override 585 public boolean check(final String key) { 586 return true; 587 } 588 } 589 590 @Override 591 public String toString() { 592 final StringBuilder sb = new StringBuilder(); 593 sb.append("facility=").append(facility.name()); 594 sb.append(" appName=").append(appName); 595 sb.append(" defaultId=").append(defaultId); 596 sb.append(" enterpriseNumber=").append(enterpriseNumber); 597 sb.append(" newLine=").append(includeNewLine); 598 sb.append(" includeMDC=").append(includeMdc); 599 sb.append(" messageId=").append(messageId); 600 return sb.toString(); 601 } 602 603 /** 604 * Create the RFC 5424 Layout. 605 * 606 * @param facility The Facility is used to try to classify the message. 607 * @param id The default structured data id to use when formatting according to RFC 5424. 608 * @param enterpriseNumber The IANA enterprise number. 609 * @param includeMDC Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog 610 * record. Defaults to "true:. 611 * @param mdcId The id to use for the MDC Structured Data Element. 612 * @param mdcPrefix The prefix to add to MDC key names. 613 * @param eventPrefix The prefix to add to event key names. 614 * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false. 615 * @param escapeNL String that should be used to replace newlines within the message text. 616 * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record. 617 * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records. 618 * @param excludes A comma separated list of MDC keys that should be excluded from the LogEvent. 619 * @param includes A comma separated list of MDC keys that should be included in the FlumeEvent. 620 * @param required A comma separated list of MDC keys that must be present in the MDC. 621 * @param exceptionPattern The pattern for formatting exceptions. 622 * @param useTlsMessageFormat If true the message will be formatted according to RFC 5425. 623 * @param loggerFields Container for the KeyValuePairs containing the patterns 624 * @param config The Configuration. Some Converters require access to the Interpolator. 625 * @return An Rfc5424Layout. 626 */ 627 @PluginFactory 628 public static Rfc5424Layout createLayout( 629 @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility, 630 @PluginAttribute("id") final String id, 631 @PluginAttribute(value = "enterpriseNumber", defaultInt = DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber, 632 @PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC, 633 @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) final String mdcId, 634 @PluginAttribute("mdcPrefix") final String mdcPrefix, 635 @PluginAttribute("eventPrefix") final String eventPrefix, 636 @PluginAttribute(value = "newLine", defaultBoolean = false) final boolean newLine, 637 @PluginAttribute("newLineEscape") final String escapeNL, 638 @PluginAttribute("appName") final String appName, 639 @PluginAttribute("messageId") final String msgId, 640 @PluginAttribute("mdcExcludes") final String excludes, 641 @PluginAttribute("mdcIncludes") String includes, 642 @PluginAttribute("mdcRequired") final String required, 643 @PluginAttribute("exceptionPattern") final String exceptionPattern, 644 @PluginAttribute(value = "useTlsMessageFormat", defaultBoolean = false) final boolean useTlsMessageFormat, // RFC 5425 645 @PluginElement("LoggerFields") final LoggerFields[] loggerFields, 646 @PluginConfiguration final Configuration config) { 647 final Charset charset = Charsets.UTF_8; 648 if (includes != null && excludes != null) { 649 LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored"); 650 includes = null; 651 } 652 653 return new Rfc5424Layout(config, facility, id, enterpriseNumber, includeMDC, newLine, escapeNL, mdcId, mdcPrefix, 654 eventPrefix, appName, msgId, excludes, includes, required, charset, exceptionPattern, 655 useTlsMessageFormat, loggerFields); 656 } 657 658 private class FieldFormatter { 659 660 private final Map<String, List<PatternFormatter>> delegateMap; 661 private final boolean discardIfEmpty; 662 663 public FieldFormatter(final Map<String, List<PatternFormatter>> fieldMap, final boolean discardIfEmpty) { 664 this.discardIfEmpty = discardIfEmpty; 665 this.delegateMap = fieldMap; 666 } 667 668 public StructuredDataElement format(final LogEvent event) { 669 final Map<String, String> map = new HashMap<String, String>(); 670 671 for (final Map.Entry<String, List<PatternFormatter>> entry : delegateMap.entrySet()) { 672 final StringBuilder buffer = new StringBuilder(); 673 for (final PatternFormatter formatter : entry.getValue()) { 674 formatter.format(event, buffer); 675 } 676 map.put(entry.getKey(), buffer.toString()); 677 } 678 return new StructuredDataElement(map, discardIfEmpty); 679 } 680 } 681 682 private class StructuredDataElement { 683 684 private final Map<String, String> fields; 685 private final boolean discardIfEmpty; 686 687 public StructuredDataElement(final Map<String, String> fields, final boolean discardIfEmpty) { 688 this.discardIfEmpty = discardIfEmpty; 689 this.fields = fields; 690 } 691 692 boolean discard() { 693 if (discardIfEmpty == false) { 694 return false; 695 } 696 boolean foundNotEmptyValue = false; 697 for (final Map.Entry<String, String> entry : fields.entrySet()) { 698 if (Strings.isNotEmpty(entry.getValue())) { 699 foundNotEmptyValue = true; 700 break; 701 } 702 } 703 return !foundNotEmptyValue; 704 } 705 706 void union(final Map<String, String> fields) { 707 this.fields.putAll(fields); 708 } 709 710 Map<String, String> getFields() { 711 return this.fields; 712 } 713 } 714 }