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 ch.qos.logback.core.util.CloseUtil; 017 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.OutputStream; 021import java.net.DatagramPacket; 022import java.net.DatagramSocket; 023import java.net.InetAddress; 024import java.net.InetSocketAddress; 025import java.net.PortUnreachableException; 026import java.net.SocketException; 027import java.net.UnknownHostException; 028 029/** 030 * SyslogOutputStream is a wrapper around the {@link DatagramSocket} class so 031 * that it behaves like an {@link OutputStream}. 032 */ 033public class SyslogOutputStream extends OutputStream { 034 035 /** 036 * The maximum length after which we discard the existing string buffer and 037 * start anew. 038 */ 039 private static final int MAX_LEN = 1024; 040 041 private InetAddress address; 042 private DatagramSocket ds; 043 private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 044 final private int port; 045 private SocketException connectException; 046 047 public SyslogOutputStream(String syslogHost, int port) throws UnknownHostException, SocketException { 048 this.address = InetAddress.getByName(syslogHost); 049 this.port = port; 050 this.ds = new DatagramSocket(); 051 // This stream only sends datagrams to the syslog host. Connecting the socket removes it from 052 // the list of unconnected (wildcard) UDP listeners reported by tools like `ss -ulnp`. 053 // See https://github.com/qos-ch/logback/issues/939 054 try { 055 this.ds.connect(new InetSocketAddress(address, port)); 056 } catch (SocketException e) { 057 // connecting may fail, e.g. if the network is not yet available. In that case 058 // fall back to an unconnected socket, i.e. the behavior prior to issue 939. 059 this.connectException = e; 060 } 061 } 062 063 boolean isValidState() { 064 if(ds == null || baos == null || address == null) { 065 return false; 066 } 067 return true; 068 } 069 070 boolean isConnected() { 071 return ds != null && ds.isConnected(); 072 } 073 074 /** 075 * Returns the exception thrown when connecting the underlying socket to the 076 * syslog host, or null if the connection succeeded. 077 */ 078 SocketException getConnectException() { 079 return connectException; 080 } 081 082 public void write(byte[] byteArray, int offset, int len) throws IOException { 083 if(!isValidState()) return; 084 baos.write(byteArray, offset, len); 085 } 086 087 public void flush() throws IOException { 088 if(!isValidState()) return; 089 byte[] bytes = baos.toByteArray(); 090 DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, port); 091 092 // clean up for next round 093 if (baos.size() > MAX_LEN) { 094 baos = new ByteArrayOutputStream(); 095 } else { 096 baos.reset(); 097 } 098 099 // after a failure, it can happen that bytes.length is zero 100 // in that case, there is no point in sending out an empty message/ 101 if (bytes.length == 0) { 102 return; 103 } 104 if (this.ds != null) { 105 try { 106 ds.send(packet); 107 } catch (PortUnreachableException e) { 108 // On a connected socket, an ICMP port unreachable message triggered by a 109 // previously sent datagram is reported on the next send, in which case the 110 // current packet is not sent. Retry once so that the current packet is not lost, 111 // as was the case with unconnected sockets. Should the retry fail as well, the 112 // exception propagates to the caller. 113 ds.send(packet); 114 } 115 } 116 117 } 118 119 @Override 120 public void close() { 121 CloseUtil.closeQuietly(ds); 122 CloseUtil.closeQuietly(baos); 123 address = null; 124 ds = null; 125 baos = null; 126 } 127 128 public int getPort() { 129 return port; 130 } 131 132 @Override 133 public void write(int b) throws IOException { 134 baos.write(b); 135 } 136 137 int getSendBufferSize() throws SocketException { 138 return ds.getSendBufferSize(); 139 } 140}