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