001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.net;
015
016import java.io.IOException;
017import java.io.OutputStream;
018import java.net.SocketException;
019import java.net.UnknownHostException;
020import java.nio.charset.Charset;
021
022import ch.qos.logback.core.AppenderBase;
023import ch.qos.logback.core.CoreConstants;
024import ch.qos.logback.core.Layout;
025
026/**
027 * Base class for SyslogAppender.
028 * 
029 * @author Ceki Gülcü
030 * 
031 * @param <E>
032 */
033public abstract class SyslogAppenderBase<E> extends AppenderBase<E> {
034
035    final static String SYSLOG_LAYOUT_URL = CoreConstants.CODES_URL + "#syslog_layout";
036    final static int MAX_MESSAGE_SIZE_LIMIT = 65000;
037
038    Layout<E> layout;
039    String facilityStr;
040    String syslogHost;
041    protected String suffixPattern;
042    SyslogOutputStream sos;
043    int port = SyslogConstants.SYSLOG_PORT;
044    int maxMessageSize;
045    Charset charset;
046
047    public void start() {
048        int errorCount = 0;
049        if (facilityStr == null) {
050            addError("The Facility option is mandatory");
051            errorCount++;
052        }
053
054        if (charset == null) {
055            // Using defaultCharset() preserves the previous behavior when String.getBytes() was
056            // called without arguments
057            charset = Charset.defaultCharset();
058        }
059
060        try {
061            sos = createOutputStream();
062
063            final int systemDatagramSize = sos.getSendBufferSize();
064            if (maxMessageSize == 0) {
065                maxMessageSize = Math.min(systemDatagramSize, MAX_MESSAGE_SIZE_LIMIT);
066                addInfo("Defaulting maxMessageSize to [" + maxMessageSize + "]");
067            } else if (maxMessageSize > systemDatagramSize) {
068                addWarn("maxMessageSize of [" + maxMessageSize + "] is larger than the system defined datagram size of [" + systemDatagramSize + "].");
069                addWarn("This may result in dropped logs.");
070            }
071        } catch (UnknownHostException e) {
072            addError("Could not create SyslogWriter", e);
073            errorCount++;
074        } catch (SocketException e) {
075            addWarn("Failed to bind to a random datagram socket. Will try to reconnect later.", e);
076        }
077
078        if (layout == null) {
079            layout = buildLayout();
080        }
081
082        if (errorCount == 0) {
083            super.start();
084        }
085    }
086
087    abstract public SyslogOutputStream createOutputStream() throws UnknownHostException, SocketException;
088
089    abstract public Layout<E> buildLayout();
090
091    abstract public int getSeverityForEvent(Object eventObject);
092
093    @Override
094    protected void append(E eventObject) {
095        if (!isStarted()) {
096            return;
097        }
098
099        try {
100            String msg = layout.doLayout(eventObject);
101            if (msg == null) {
102                return;
103            }
104            if (msg.length() > maxMessageSize) {
105                msg = msg.substring(0, maxMessageSize);
106            }
107            sos.write(msg.getBytes(charset));
108            sos.flush();
109            postProcess(eventObject, sos);
110        } catch (IOException ioe) {
111            addError("Failed to send diagram to " + syslogHost, ioe);
112        }
113    }
114
115    protected void postProcess(Object event, OutputStream sw) {
116
117    }
118
119    /**
120     * Returns the integer value corresponding to the named syslog facility.
121     * 
122     * @throws IllegalArgumentException
123     *           if the facility string is not recognized
124     */
125    static public int facilityStringToint(String facilityStr) {
126        if ("KERN".equalsIgnoreCase(facilityStr)) {
127            return SyslogConstants.LOG_KERN;
128        } else if ("USER".equalsIgnoreCase(facilityStr)) {
129            return SyslogConstants.LOG_USER;
130        } else if ("MAIL".equalsIgnoreCase(facilityStr)) {
131            return SyslogConstants.LOG_MAIL;
132        } else if ("DAEMON".equalsIgnoreCase(facilityStr)) {
133            return SyslogConstants.LOG_DAEMON;
134        } else if ("AUTH".equalsIgnoreCase(facilityStr)) {
135            return SyslogConstants.LOG_AUTH;
136        } else if ("SYSLOG".equalsIgnoreCase(facilityStr)) {
137            return SyslogConstants.LOG_SYSLOG;
138        } else if ("LPR".equalsIgnoreCase(facilityStr)) {
139            return SyslogConstants.LOG_LPR;
140        } else if ("NEWS".equalsIgnoreCase(facilityStr)) {
141            return SyslogConstants.LOG_NEWS;
142        } else if ("UUCP".equalsIgnoreCase(facilityStr)) {
143            return SyslogConstants.LOG_UUCP;
144        } else if ("CRON".equalsIgnoreCase(facilityStr)) {
145            return SyslogConstants.LOG_CRON;
146        } else if ("AUTHPRIV".equalsIgnoreCase(facilityStr)) {
147            return SyslogConstants.LOG_AUTHPRIV;
148        } else if ("FTP".equalsIgnoreCase(facilityStr)) {
149            return SyslogConstants.LOG_FTP;
150        } else if ("NTP".equalsIgnoreCase(facilityStr)) {
151            return SyslogConstants.LOG_NTP;
152        } else if ("AUDIT".equalsIgnoreCase(facilityStr)) {
153            return SyslogConstants.LOG_AUDIT;
154        } else if ("ALERT".equalsIgnoreCase(facilityStr)) {
155            return SyslogConstants.LOG_ALERT;
156        } else if ("CLOCK".equalsIgnoreCase(facilityStr)) {
157            return SyslogConstants.LOG_CLOCK;
158        } else if ("LOCAL0".equalsIgnoreCase(facilityStr)) {
159            return SyslogConstants.LOG_LOCAL0;
160        } else if ("LOCAL1".equalsIgnoreCase(facilityStr)) {
161            return SyslogConstants.LOG_LOCAL1;
162        } else if ("LOCAL2".equalsIgnoreCase(facilityStr)) {
163            return SyslogConstants.LOG_LOCAL2;
164        } else if ("LOCAL3".equalsIgnoreCase(facilityStr)) {
165            return SyslogConstants.LOG_LOCAL3;
166        } else if ("LOCAL4".equalsIgnoreCase(facilityStr)) {
167            return SyslogConstants.LOG_LOCAL4;
168        } else if ("LOCAL5".equalsIgnoreCase(facilityStr)) {
169            return SyslogConstants.LOG_LOCAL5;
170        } else if ("LOCAL6".equalsIgnoreCase(facilityStr)) {
171            return SyslogConstants.LOG_LOCAL6;
172        } else if ("LOCAL7".equalsIgnoreCase(facilityStr)) {
173            return SyslogConstants.LOG_LOCAL7;
174        } else {
175            throw new IllegalArgumentException(facilityStr + " is not a valid syslog facility string");
176        }
177    }
178
179    /**
180     * Returns the value of the <b>SyslogHost</b> option.
181     */
182    public String getSyslogHost() {
183        return syslogHost;
184    }
185
186    /**
187     * The <b>SyslogHost</b> option is the name of the the syslog host where log
188     * output should go.
189     * 
190     * <b>WARNING</b> If the SyslogHost is not set, then this appender will fail.
191     */
192    public void setSyslogHost(String syslogHost) {
193        this.syslogHost = syslogHost;
194    }
195
196    /**
197     * Returns the string value of the <b>Facility</b> option.
198     * 
199     * See {@link #setFacility} for the set of allowed values.
200     */
201    public String getFacility() {
202        return facilityStr;
203    }
204
205    /**
206     * The <b>Facility</b> option must be set one of the strings KERN, USER, MAIL,
207     * DAEMON, AUTH, SYSLOG, LPR, NEWS, UUCP, CRON, AUTHPRIV, FTP, NTP, AUDIT,
208     * ALERT, CLOCK, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6,
209     * LOCAL7. Case is not important.
210     * 
211     * <p>
212     * See {@link ch.qos.logback.core.net.SyslogConstants SyslogConstants} and RFC 3164 for more information about the
213     * <b>Facility</b> option.
214     */
215    public void setFacility(String facilityStr) {
216        if (facilityStr != null) {
217            facilityStr = facilityStr.trim();
218        }
219        this.facilityStr = facilityStr;
220    }
221
222    /**
223     * 
224     * @return
225     */
226    public int getPort() {
227        return port;
228    }
229
230    /**
231     * The port number on the syslog server to connect to. Normally, you would not
232     * want to change the default value, that is 514.
233     */
234    public void setPort(int port) {
235        this.port = port;
236    }
237
238    public int getMaxMessageSize() {
239        return maxMessageSize;
240    }
241
242    /**
243     * Maximum size for the syslog message (in characters); messages
244     * longer than this are truncated. The default value is 65400 (which
245     * is near the maximum for syslog-over-UDP). Note that the value is
246     * characters; the number of bytes may vary if non-ASCII characters
247     * are present.
248     */
249    public void setMaxMessageSize(int maxMessageSize) {
250        this.maxMessageSize = maxMessageSize;
251    }
252
253    public Layout<E> getLayout() {
254        return layout;
255    }
256
257    public void setLayout(Layout<E> layout) {
258        addWarn("The layout of a SyslogAppender cannot be set directly. See also " + SYSLOG_LAYOUT_URL);
259    }
260
261    @Override
262    public void stop() {
263        if (sos != null) {
264            sos.close();
265        }
266        super.stop();
267    }
268
269    /**
270       * See {@link #setSuffixPattern #setSuffixPattern(String)}.
271       * 
272       * @return
273       */
274    public String getSuffixPattern() {
275        return suffixPattern;
276    }
277
278    /**
279     * The <b>suffixPattern</b> option specifies the format of the
280     * non-standardized part of the message sent to the syslog server.
281     * 
282     * @param suffixPattern
283     */
284    public void setSuffixPattern(String suffixPattern) {
285        this.suffixPattern = suffixPattern;
286    }
287
288    /**
289     * Returns the Charset used to encode String messages into byte sequences when writing to
290     * syslog.
291     */
292    public Charset getCharset() {
293        return charset;
294    }
295
296    /**
297     * The Charset to use when encoding messages into byte sequences.
298     *
299     * @param charset
300     */
301    public void setCharset(Charset charset) {
302        this.charset = charset;
303    }
304}