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