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