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}