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 org.apache.logging.log4j.LoggingException;
20 import org.apache.logging.log4j.core.LogEvent;
21 import org.apache.logging.log4j.core.config.Configuration;
22 import org.apache.logging.log4j.core.config.plugins.Plugin;
23 import org.apache.logging.log4j.core.config.plugins.PluginAttr;
24 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
25 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
26 import org.apache.logging.log4j.core.helpers.Charsets;
27 import org.apache.logging.log4j.core.helpers.NetUtils;
28 import org.apache.logging.log4j.core.net.Facility;
29 import org.apache.logging.log4j.core.net.Priority;
30 import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
31 import org.apache.logging.log4j.core.pattern.PatternFormatter;
32 import org.apache.logging.log4j.core.pattern.PatternParser;
33 import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
34 import org.apache.logging.log4j.message.Message;
35 import org.apache.logging.log4j.message.StructuredDataId;
36 import org.apache.logging.log4j.message.StructuredDataMessage;
37
38 import java.nio.charset.Charset;
39 import java.util.ArrayList;
40 import java.util.Calendar;
41 import java.util.GregorianCalendar;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.SortedMap;
45 import java.util.TreeMap;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48
49
50
51
52
53 @Plugin(name = "RFC5424Layout", type = "Core", elementType = "layout", printObject = true)
54 public final class RFC5424Layout extends AbstractStringLayout {
55
56
57
58
59 public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
60
61
62
63 public static final String DEFAULT_ID = "Audit";
64
65
66
67 public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
68
69 private static final String DEFAULT_MDCID = "mdc";
70 private static final int TWO_DIGITS = 10;
71 private static final int THREE_DIGITS = 100;
72 private static final int MILLIS_PER_MINUTE = 60000;
73 private static final int MINUTES_PER_HOUR = 60;
74
75 private static final String COMPONENT_KEY = "RFC5424-Converter";
76
77 private final Facility facility;
78 private final String defaultId;
79 private final Integer enterpriseNumber;
80 private final boolean includeMDC;
81 private final String mdcId;
82 private final String localHostName;
83 private final String appName;
84 private final String messageId;
85 private final String configName;
86 private final List<String> mdcExcludes;
87 private final List<String> mdcIncludes;
88 private final List<String> mdcRequired;
89 private final ListChecker checker;
90 private final ListChecker noopChecker = new NoopChecker();
91 private final boolean includeNewLine;
92 private final String escapeNewLine;
93
94 private long lastTimestamp = -1;
95 private String timestamppStr;
96
97 private final List<PatternFormatter> formatters;
98
99 private RFC5424Layout(final Configuration config, final Facility facility, final String id, final int ein,
100 final boolean includeMDC, final boolean includeNL, final String escapeNL, final String mdcId,
101 final String appName, final String messageId, final String excludes, final String includes,
102 final String required, final Charset charset, final String exceptionPattern) {
103 super(charset);
104 final PatternParser parser = createPatternParser(config);
105 formatters = exceptionPattern == null ? null : parser.parse(exceptionPattern, false);
106 this.facility = facility;
107 this.defaultId = id == null ? DEFAULT_ID : id;
108 this.enterpriseNumber = ein;
109 this.includeMDC = includeMDC;
110 this.includeNewLine = includeNL;
111 this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
112 this.mdcId = mdcId;
113 this.appName = appName;
114 this.messageId = messageId;
115 this.localHostName = NetUtils.getLocalHostname();
116 ListChecker c = null;
117 if (excludes != null) {
118 final String[] array = excludes.split(",");
119 if (array.length > 0) {
120 c = new ExcludeChecker();
121 mdcExcludes = new ArrayList<String>(array.length);
122 for (final String str : array) {
123 mdcExcludes.add(str.trim());
124 }
125 } else {
126 mdcExcludes = null;
127 }
128 } else {
129 mdcExcludes = null;
130 }
131 if (includes != null) {
132 final String[] array = includes.split(",");
133 if (array.length > 0) {
134 c = new IncludeChecker();
135 mdcIncludes = new ArrayList<String>(array.length);
136 for (final String str : array) {
137 mdcIncludes.add(str.trim());
138 }
139 } else {
140 mdcIncludes = null;
141 }
142 } else {
143 mdcIncludes = null;
144 }
145 if (required != null) {
146 final String[] array = required.split(",");
147 if (array.length > 0) {
148 mdcRequired = new ArrayList<String>(array.length);
149 for (final String str : array) {
150 mdcRequired.add(str.trim());
151 }
152 } else {
153 mdcRequired = null;
154 }
155
156 } else {
157 mdcRequired = null;
158 }
159 this.checker = c != null ? c : noopChecker;
160 final String name = config == null ? null : config.getName();
161 configName = name != null && name.length() > 0 ? name : null;
162 }
163
164
165
166
167
168
169 public static PatternParser createPatternParser(final Configuration config) {
170 if (config == null) {
171 return new PatternParser(config, PatternLayout.KEY, LogEventPatternConverter.class,
172 ThrowablePatternConverter.class);
173 }
174 PatternParser parser = (PatternParser) config.getComponent(COMPONENT_KEY);
175 if (parser == null) {
176 parser = new PatternParser(config, PatternLayout.KEY, ThrowablePatternConverter.class);
177 config.addComponent(COMPONENT_KEY, parser);
178 parser = (PatternParser) config.getComponent(COMPONENT_KEY);
179 }
180 return parser;
181 }
182
183
184
185
186
187
188
189 public String toSerializable(final LogEvent event) {
190 final Message msg = event.getMessage();
191 final boolean isStructured = msg instanceof StructuredDataMessage;
192 final StringBuilder buf = new StringBuilder();
193
194 buf.append("<");
195 buf.append(Priority.getPriority(facility, event.getLevel()));
196 buf.append(">1 ");
197 buf.append(computeTimeStampString(event.getMillis()));
198 buf.append(' ');
199 buf.append(localHostName);
200 buf.append(' ');
201 if (appName != null) {
202 buf.append(appName);
203 } else if (configName != null) {
204 buf.append(configName);
205 } else {
206 buf.append("-");
207 }
208 buf.append(" ");
209 buf.append(getProcId());
210 buf.append(" ");
211 final String type = isStructured ? ((StructuredDataMessage) msg).getType() : null;
212 if (type != null) {
213 buf.append(type);
214 } else if (messageId != null) {
215 buf.append(messageId);
216 } else {
217 buf.append("-");
218 }
219 buf.append(" ");
220 if (isStructured || includeMDC) {
221 StructuredDataId id = null;
222 String text;
223 if (isStructured) {
224 final StructuredDataMessage data = (StructuredDataMessage) msg;
225 final Map<String, String> map = data.getData();
226 id = data.getId();
227 formatStructuredElement(id, map, buf, noopChecker);
228 text = data.getFormat();
229 } else {
230 text = msg.getFormattedMessage();
231 }
232 if (includeMDC) {
233 if (mdcRequired != null) {
234 checkRequired(event.getContextMap());
235 }
236 final int ein = id == null || id.getEnterpriseNumber() < 0 ?
237 enterpriseNumber : id.getEnterpriseNumber();
238 final StructuredDataId mdcSDID = new StructuredDataId(mdcId, ein, null, null);
239 formatStructuredElement(mdcSDID, event.getContextMap(), buf, checker);
240 }
241 if (text != null && text.length() > 0) {
242 buf.append(" ").append(escapeNewlines(text, escapeNewLine));
243 }
244 } else {
245 buf.append("- ");
246 buf.append(escapeNewlines(msg.getFormattedMessage(), escapeNewLine));
247 }
248 if (formatters != null && event.getThrown() != null) {
249 final StringBuilder exception = new StringBuilder("\n");
250 for (final PatternFormatter formatter : formatters) {
251 formatter.format(event, exception);
252 }
253 buf.append(escapeNewlines(exception.toString(), escapeNewLine));
254 }
255 if (includeNewLine) {
256 buf.append("\n");
257 }
258 return buf.toString();
259 }
260
261 private String escapeNewlines(final String text, final String escapeNewLine)
262 {
263 if (null == escapeNewLine) {
264 return text;
265 }
266 return NEWLINE_PATTERN.matcher(text).replaceAll(escapeNewLine);
267 }
268
269 protected String getProcId() {
270 return "-";
271 }
272
273 protected List<String> getMdcExcludes() {
274 return mdcExcludes;
275 }
276
277 protected List<String> getMdcIncludes() {
278 return mdcIncludes;
279 }
280
281 private String computeTimeStampString(final long now) {
282 long last;
283 synchronized (this) {
284 last = lastTimestamp;
285 if (now == lastTimestamp) {
286 return timestamppStr;
287 }
288 }
289
290 final StringBuilder buf = new StringBuilder();
291 final Calendar cal = new GregorianCalendar();
292 cal.setTimeInMillis(now);
293 buf.append(Integer.toString(cal.get(Calendar.YEAR)));
294 buf.append("-");
295 pad(cal.get(Calendar.MONTH) + 1, TWO_DIGITS, buf);
296 buf.append("-");
297 pad(cal.get(Calendar.DAY_OF_MONTH), TWO_DIGITS, buf);
298 buf.append("T");
299 pad(cal.get(Calendar.HOUR_OF_DAY), TWO_DIGITS, buf);
300 buf.append(":");
301 pad(cal.get(Calendar.MINUTE), TWO_DIGITS, buf);
302 buf.append(":");
303 pad(cal.get(Calendar.SECOND), TWO_DIGITS, buf);
304
305 final int millis = cal.get(Calendar.MILLISECOND);
306 if (millis != 0) {
307 buf.append('.');
308 pad(millis, THREE_DIGITS, buf);
309 }
310
311 int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / MILLIS_PER_MINUTE;
312 if (tzmin == 0) {
313 buf.append("Z");
314 } else {
315 if (tzmin < 0) {
316 tzmin = -tzmin;
317 buf.append("-");
318 } else {
319 buf.append("+");
320 }
321 final int tzhour = tzmin / MINUTES_PER_HOUR;
322 tzmin -= tzhour * MINUTES_PER_HOUR;
323 pad(tzhour, TWO_DIGITS, buf);
324 buf.append(":");
325 pad(tzmin, TWO_DIGITS, buf);
326 }
327 synchronized (this) {
328 if (last == lastTimestamp) {
329 lastTimestamp = now;
330 timestamppStr = buf.toString();
331 }
332 }
333 return buf.toString();
334 }
335
336 private void pad(final int val, int max, final StringBuilder buf) {
337 while (max > 1) {
338 if (val < max) {
339 buf.append("0");
340 }
341 max = max / TWO_DIGITS;
342 }
343 buf.append(Integer.toString(val));
344 }
345
346 private void formatStructuredElement(final StructuredDataId id, final Map<String, String> data,
347 final StringBuilder sb, final ListChecker checker) {
348 if (id == null && defaultId == null) {
349 return;
350 }
351 sb.append("[");
352 sb.append(getId(id));
353 appendMap(data, sb, checker);
354 sb.append("]");
355 }
356
357 private String getId(final StructuredDataId id) {
358 final StringBuilder sb = new StringBuilder();
359 if (id.getName() == null) {
360 sb.append(defaultId);
361 } else {
362 sb.append(id.getName());
363 }
364 int ein = id.getEnterpriseNumber();
365 if (ein < 0) {
366 ein = enterpriseNumber;
367 }
368 if (ein >= 0) {
369 sb.append("@").append(ein);
370 }
371 return sb.toString();
372 }
373
374 private void checkRequired(final Map<String, String> map) {
375 for (final String key : mdcRequired) {
376 final String value = map.get(key);
377 if (value == null) {
378 throw new LoggingException("Required key " + key + " is missing from the " + mdcId);
379 }
380 }
381 }
382
383 private void appendMap(final Map<String, String> map, final StringBuilder sb, final ListChecker checker)
384 {
385 final SortedMap<String, String> sorted = new TreeMap<String, String>(map);
386 for (final Map.Entry<String, String> entry : sorted.entrySet()) {
387 if (checker.check(entry.getKey()) && entry.getValue() != null) {
388 sb.append(" ");
389 sb.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
390 }
391 }
392 }
393
394
395
396
397 private interface ListChecker {
398 boolean check(String key);
399 }
400
401
402
403
404 private class IncludeChecker implements ListChecker {
405 public boolean check(final String key) {
406 return mdcIncludes.contains(key);
407 }
408 }
409
410
411
412
413 private class ExcludeChecker implements ListChecker {
414 public boolean check(final String key) {
415 return !mdcExcludes.contains(key);
416 }
417 }
418
419
420
421
422 private class NoopChecker implements ListChecker {
423 public boolean check(final String key) {
424 return true;
425 }
426 }
427
428 @Override
429 public String toString() {
430 final StringBuilder sb = new StringBuilder();
431 sb.append("facility=").append(facility.name());
432 sb.append(" appName=").append(appName);
433 sb.append(" defaultId=").append(defaultId);
434 sb.append(" enterpriseNumber=").append(enterpriseNumber);
435 sb.append(" newLine=").append(includeNewLine);
436 sb.append(" includeMDC=").append(includeMDC);
437 sb.append(" messageId=").append(messageId);
438 return sb.toString();
439 }
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461 @PluginFactory
462 public static RFC5424Layout createLayout(@PluginAttr("facility") final String facility,
463 @PluginAttr("id") final String id,
464 @PluginAttr("enterpriseNumber") final String ein,
465 @PluginAttr("includeMDC") final String includeMDC,
466 @PluginAttr("mdcId") String mdcId,
467 @PluginAttr("newLine") final String includeNL,
468 @PluginAttr("newLineEscape") final String escapeNL,
469 @PluginAttr("appName") final String appName,
470 @PluginAttr("messageId") final String msgId,
471 @PluginAttr("mdcExcludes") final String excludes,
472 @PluginAttr("mdcIncludes") String includes,
473 @PluginAttr("mdcRequired") final String required,
474 @PluginAttr("charset") final String charsetName,
475 @PluginAttr("exceptionPattern") final String exceptionPattern,
476 @PluginConfiguration final Configuration config) {
477 final Charset charset = Charsets.getSupportedCharset(charsetName);
478 if (includes != null && excludes != null) {
479 LOGGER.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
480 includes = null;
481 }
482 final Facility f = Facility.toFacility(facility, Facility.LOCAL0);
483 final int enterpriseNumber = ein == null ? DEFAULT_ENTERPRISE_NUMBER : Integer.parseInt(ein);
484 final boolean isMdc = includeMDC == null ? true : Boolean.valueOf(includeMDC);
485 final boolean includeNewLine = includeNL == null ? false : Boolean.valueOf(includeNL);
486 if (mdcId == null) {
487 mdcId = DEFAULT_MDCID;
488 }
489
490 return new RFC5424Layout(config, f, id, enterpriseNumber, isMdc, includeNewLine, escapeNL, mdcId, appName,
491 msgId, excludes, includes, required, charset, exceptionPattern);
492 }
493 }