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 org.apache.logging.log4j.core.LogEvent;
020    import org.apache.logging.log4j.core.config.plugins.Plugin;
021    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
022    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
023    import org.apache.logging.log4j.core.helpers.Charsets;
024    import org.apache.logging.log4j.core.net.Facility;
025    import org.apache.logging.log4j.core.net.Priority;
026    
027    import java.net.InetAddress;
028    import java.net.UnknownHostException;
029    import java.nio.charset.Charset;
030    import java.text.SimpleDateFormat;
031    import java.util.Date;
032    import java.util.Locale;
033    import java.util.regex.Matcher;
034    import java.util.regex.Pattern;
035    
036    
037    /**
038     * Formats a log event as a BSD Log record.
039     */
040    @Plugin(name = "SyslogLayout", type = "Core", elementType = "layout", printObject = true)
041    public class SyslogLayout extends AbstractStringLayout {
042        /**
043         * Match newlines in a platform-independent manner.
044         */
045        public static final Pattern NEWLINE_PATTERN = Pattern.compile("\\r?\\n");
046    
047        private final Facility facility;
048        private final boolean includeNewLine;
049        private final String escapeNewLine;
050    
051        /**
052         * Date format used if header = true.
053         */
054        private final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH);
055        /**
056         * Host name used to identify messages from this appender.
057         */
058        private final String localHostname = getLocalHostname();
059    
060    
061    
062        protected SyslogLayout(final Facility facility, final boolean includeNL, final String escapeNL, final Charset c) {
063            super(c);
064            this.facility = facility;
065            this.includeNewLine = includeNL;
066            this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
067        }
068    
069        /**
070         * Formats a {@link org.apache.logging.log4j.core.LogEvent} in conformance with the log4j.dtd.
071         *
072         * @param event The LogEvent
073         * @return the event formatted as a String.
074         */
075        public String toSerializable(final LogEvent event) {
076            final StringBuilder buf = new StringBuilder();
077    
078            buf.append("<");
079            buf.append(Priority.getPriority(facility, event.getLevel()));
080            buf.append(">");
081            addDate(event.getMillis(), buf);
082            buf.append(" ");
083            buf.append(localHostname);
084            buf.append(" ");
085    
086            String message = event.getMessage().getFormattedMessage();
087            if (null != escapeNewLine) {
088                message = NEWLINE_PATTERN.matcher(message).replaceAll(escapeNewLine);
089            }
090            buf.append(message);
091    
092            if (includeNewLine) {
093                buf.append("\n");
094            }
095            return buf.toString();
096        }
097    
098        /**
099         * This method gets the network name of the machine we are running on.
100         * Returns "UNKNOWN_LOCALHOST" in the unlikely case where the host name
101         * cannot be found.
102         *
103         * @return String the name of the local host
104         */
105        private String getLocalHostname() {
106            try {
107                final InetAddress addr = InetAddress.getLocalHost();
108                return addr.getHostName();
109            } catch (final UnknownHostException uhe) {
110                LOGGER.error("Could not determine local host name", uhe);
111                return "UNKNOWN_LOCALHOST";
112            }
113        }
114    
115        private synchronized void addDate(final long timestamp, final StringBuilder buf) {
116            final int index = buf.length() + 4;
117            buf.append(dateFormat.format(new Date(timestamp)));
118            //  RFC 3164 says leading space, not leading zero on days 1-9
119            if (buf.charAt(index) == '0') {
120                buf.setCharAt(index, ' ');
121            }
122        }
123    
124        /**
125         * Create a SyslogLayout.
126         * @param facility The Facility is used to try to classify the message.
127         * @param includeNL If true a newline will be appended to the result.
128         * @param escapeNL Pattern to use for replacing newlines.
129         * @param charsetName The character set.
130         * @return A SyslogLayout.
131         */
132        @PluginFactory
133        public static SyslogLayout createLayout(@PluginAttr("facility") final String facility,
134                                                @PluginAttr("newLine") final String includeNL,
135                                                @PluginAttr("newLineEscape") final String escapeNL,
136                                                @PluginAttr("charset") final String charsetName) {
137            final Charset charset = Charsets.getSupportedCharset(charsetName);
138            final boolean includeNewLine = includeNL == null ? false : Boolean.valueOf(includeNL);
139            final Facility f = Facility.toFacility(facility, Facility.LOCAL0);
140            return new SyslogLayout(f, includeNewLine, escapeNL, charset);
141        }
142    }