blob: 496e58f67dc211fa1e5f02ee6d8ae09c02870537 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.layout;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttr;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.net.Facility;
import org.apache.logging.log4j.core.net.Priority;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.StructuredDataId;
import org.apache.logging.log4j.message.StructuredDataMessage;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
/**
*
*/
@Plugin(name="RFC5424Layout",type="Core",elementType="layout",printObject=true)
public class RFC5424Layout extends LayoutBase {
private final Facility facility;
private final String defaultId;
private final Integer enterpriseNumber;
private final boolean includeMDC;
private final String mdcId;
private final Charset charset;
private final String localHostName;
private final String appName;
private final String messageId;
private final String configName;
private final List<String> mdcExcludes;
private final List<String> mdcIncludes;
private final ListChecker checker;
private final ListChecker noopChecker = new NoopChecker();
private final boolean includeNewLine;
private long lastTimestamp = -1;
private String timestamppStr = null;
private static final DecimalFormat TWO_DIGIT = new DecimalFormat("00");
private static final DecimalFormat FOUR_DIGIT = new DecimalFormat("0000");
// Not a very good default - it is the Apache Software Foundation's.
public static final int DEFAULT_ENTERPRISE_NUMBER = 18060;
public static final String DEFAULT_ID = "Audit";
public RFC5424Layout(Facility facility, String id, int ein, boolean includeMDC, boolean includeNL, String mdcId,
String appName, String messageId, String excludes, String includes, Charset charset) {
this.facility = facility;
this.defaultId = id == null ? DEFAULT_ID : id;
this.enterpriseNumber = ein;
this.includeMDC = includeMDC;
this.includeNewLine = includeNL;
this.mdcId = mdcId;
this.appName = appName;
this.messageId = messageId;
this.charset = charset;
this.localHostName = getLocalHostname();
ListChecker c = null;
if (excludes != null) {
String[] array = excludes.split(",");
if (array.length > 0) {
c = new ExcludeChecker();
mdcExcludes = new ArrayList<String>(array.length);
for (String str : array) {
mdcExcludes.add(str.trim());
}
} else {
mdcExcludes = null;
}
} else {
mdcExcludes = null;
}
if (includes != null) {
String[] array = includes.split(",");
if (array.length > 0) {
c = new IncludeChecker();
mdcIncludes = new ArrayList<String>(array.length);
for (String str : array) {
mdcIncludes.add(str.trim());
}
} else {
mdcIncludes = null;
}
} else {
mdcIncludes = null;
}
this.checker = c != null ? c : noopChecker;
LoggerContext ctx = (LoggerContext) LogManager.getContext();
String name = ctx.getConfiguration().getName();
configName = (name != null && name.length() > 0) ? name : null;
}
/**
* Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the RFC 5424 Syslog specification.
*/
public byte[] format(final LogEvent event) {
Message msg = event.getMessage();
boolean isStructured = msg instanceof StructuredDataMessage;
StringBuilder buf = new StringBuilder();
buf.append("<");
buf.append(Priority.getPriority(facility, event.getLevel()));
buf.append(">1 ");
buf.append(computeTimeStampString(event.getMillis()));
buf.append(' ');
buf.append(localHostName);
buf.append(' ');
if (appName != null) {
buf.append(appName);
} else if (configName != null) {
buf.append(configName);
} else {
buf.append("-");
}
buf.append(" ");
buf.append(getProcId());
buf.append(" ");
String type = isStructured ? ((StructuredDataMessage) msg).getType() : null;
if (type != null) {
buf.append(type);
} else if (messageId != null) {
buf.append(messageId);
} else {
buf.append("-");
}
buf.append(" ");
if (isStructured) {
StructuredDataMessage data = (StructuredDataMessage) msg;
Map map = data.getData();
StructuredDataId id = data.getId();
formatStructuredElement(id, map, buf, noopChecker);
if (includeMDC)
{
int ein = id.getEnterpriseNumber() < 0 ? enterpriseNumber : id.getEnterpriseNumber();
StructuredDataId mdcSDID = new StructuredDataId(mdcId, ein, null, null);
formatStructuredElement(mdcSDID, event.getContextMap(), buf, checker);
}
String text = data.getMessageFormat();
if (text != null && text.length() > 0) {
buf.append(" ").append(text);
}
} else {
buf.append("- ");
buf.append(event.getMessage().getFormattedMessage());
}
if (includeNewLine) {
buf.append("\n");
}
return buf.toString().getBytes(charset);
}
protected String getProcId() {
return "-";
}
/**
* This method gets the network name of the machine we are running on.
* Returns "UNKNOWN_LOCALHOST" in the unlikely case where the host name
* cannot be found.
*
* @return String the name of the local host
*/
public String getLocalHostname() {
try {
InetAddress addr = InetAddress.getLocalHost();
return addr.getHostName();
} catch (UnknownHostException uhe) {
logger.error("Could not determine local host name", uhe);
return "UNKNOWN_LOCALHOST";
}
}
private String computeTimeStampString(long now) {
long last;
synchronized (this) {
last = lastTimestamp;
if (now == lastTimestamp) {
return timestamppStr;
}
}
StringBuilder buf = new StringBuilder();
Calendar cal = new GregorianCalendar();
cal.setTimeInMillis(now);
buf.append(Integer.toString(cal.get(Calendar.YEAR)));
buf.append("-");
pad(cal.get(Calendar.MONTH) + 1, 10, buf);
buf.append("-");
pad(cal.get(Calendar.DAY_OF_MONTH), 10, buf);
buf.append("T");
pad(cal.get(Calendar.HOUR_OF_DAY), 10, buf);
buf.append(":");
pad(cal.get(Calendar.MINUTE), 10, buf);
buf.append(":");
pad(cal.get(Calendar.SECOND), 10, buf);
int millis = cal.get(Calendar.MILLISECOND);
if (millis != 0) {
buf.append(".");
pad((int) ((float) millis / 10F), 100, buf);
}
int tzmin = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 60000;
if (tzmin == 0) {
buf.append("Z");
} else {
if (tzmin < 0) {
tzmin = -tzmin;
buf.append("-");
} else {
buf.append("+");
}
int tzhour = tzmin / 60;
tzmin -= tzhour * 60;
pad(tzhour, 10, buf);
buf.append(":");
pad(tzmin, 10, buf);
}
synchronized (this) {
if (last == lastTimestamp) {
lastTimestamp = now;
timestamppStr = buf.toString();
}
}
return buf.toString();
}
private void pad(int val, int max, StringBuilder buf) {
while (max > 1) {
if (val < max) {
buf.append("0");
}
max = max / 10;
}
buf.append(Integer.toString(val));
}
private void formatStructuredElement(StructuredDataId id, Map<String, Object> data, StringBuilder sb,
ListChecker checker)
{
if (id == null && defaultId == null)
{
return;
}
sb.append("[");
sb.append(getId(id));
appendMap(data, sb, checker);
sb.append("]");
}
private String getId(StructuredDataId id) {
StringBuilder sb = new StringBuilder();
if (id.getName() == null) {
sb.append(defaultId);
} else {
sb.append(id.getName());
}
int ein = id.getEnterpriseNumber();
if (ein < 0) {
ein = enterpriseNumber;
}
if (ein >=0) {
sb.append("@").append(ein);
}
return sb.toString();
}
private void appendMap(Map<String, Object> map, StringBuilder sb, ListChecker checker)
{
SortedMap<String, Object> sorted = new TreeMap<String, Object>(map);
for (Map.Entry<String, Object> entry : sorted.entrySet())
{
if (checker.check(entry.getKey())) {
sb.append(" ");
sb.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
}
}
}
private interface ListChecker {
boolean check(String key);
}
private class IncludeChecker implements ListChecker {
public boolean check(String key) {
return mdcIncludes.contains(key);
}
}
private class ExcludeChecker implements ListChecker {
public boolean check(String key) {
return !mdcExcludes.contains(key);
}
}
public class NoopChecker implements ListChecker {
public boolean check(String key) {
return true;
}
}
@PluginFactory
public static RFC5424Layout createLayout(@PluginAttr("facility") String facility,
@PluginAttr("id") String id,
@PluginAttr("enterpriseNumber") String ein,
@PluginAttr("includeMDC") String includeMDC,
@PluginAttr("mdcId") String mdcId,
@PluginAttr("newLine") String includeNL,
@PluginAttr("appName") String appName,
@PluginAttr("messageId") String msgId,
@PluginAttr("mdcExcludes") String excludes,
@PluginAttr("mdcINcludes") String includes,
@PluginAttr("charset") String charset) {
Charset c = Charset.isSupported("UTF-8") ? Charset.forName("UTF-8") : Charset.defaultCharset();
if (charset != null) {
if (Charset.isSupported(charset)) {
c = Charset.forName(charset);
} else {
logger.error("Charset " + charset + " is not supported for layout, using " + c.displayName());
}
}
if (includes != null && excludes != null) {
logger.error("mdcIncludes and mdcExcludes are mutually exclusive. Includes wil be ignored");
includes = null;
}
Facility f = facility != null ? Facility.valueOf(facility.toUpperCase()) : Facility.LOCAL0;
int enterpriseNumber = ein == null ? DEFAULT_ENTERPRISE_NUMBER : Integer.parseInt(ein);
boolean isMdc = includeMDC == null ? true : Boolean.valueOf(includeMDC);
boolean includeNewLine = includeNL == null ? false : Boolean.valueOf(includeNL);
return new RFC5424Layout(f, id, enterpriseNumber, isMdc, includeNewLine, mdcId, appName, msgId, excludes,
includes, c);
}
}