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