java · 2025-08-31 0

logback 发送 tcp syslog 日志

1.SyslogAppender

logback 有 SyslogAppender,发送 syslog 日志,但是只支持发送 udp syslog 日志。需自定义 Appender,发送 tcp syslog 日志。

2.自定义 Appender

pom 文件

<!-- slf4j依赖包 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>

<!-- logback-classic桥接器 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

<!-- logback实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.11</version>
</dependency>
public class CustomSyslogAppender extends SyslogAppender {

    @Override
    public SyslogOutputStream createOutputStream() throws SocketException, UnknownHostException {
        return new CustomSyslogOutputStream(getSyslogHost(), getPort());
    }

    public static class CustomSyslogOutputStream extends SyslogOutputStream {

        private static final int MAX_LEN = 1024;

        private final String syslogHost;

        private final int port;

        private Socket socket;

        private OutputStream outputStream;

        private ByteArrayOutputStream baos = new ByteArrayOutputStream();

        public CustomSyslogOutputStream(String syslogHost, int port) throws UnknownHostException, SocketException {
            super(syslogHost, port);
            this.syslogHost = syslogHost;
            this.port = port;
            // 建立 TCP 连接(延迟到第一次 flush 时再连接,避免初始化失败阻塞)
        }

        @Override
        public void write(int b) throws IOException {
            baos.write(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            baos.write(b, off, len);
        }

        @Override
        public void flush() throws IOException {
            if (baos.size() == 0) {
                return;
            }

            byte[] bytes = baos.toByteArray();
            if (bytes.length == 0) {
                return;
            }

            // 确保连接
            ensureConnected();

            try {
                outputStream.write(bytes);

                // 可选:自动添加换行符(很多 Syslog 服务器依赖换行符分隔日志)
                outputStream.write('\n');
                outputStream.flush();
            } catch (IOException e) {
                // 连接出错,关闭 socket,下次重连
                closeQuietly();
                throw e;
            }

            // 重置缓冲区
            if (baos.size() > MAX_LEN) {
                baos = new ByteArrayOutputStream();
            } else {
                baos.reset();
            }
        }

        @Override
        public void close() {
            closeQuietly();
        }

        /**
         * 确保连接已建立
         */
        private synchronized void ensureConnected() throws IOException {
            if (socket == null || socket.isClosed() || !socket.isConnected()) {
                socket = new Socket(syslogHost, port);
                outputStream = socket.getOutputStream();
            }
        }

        private void closeQuietly() {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException ignored) {
            }
            try {
                if (socket != null && !socket.isClosed()) {
                    socket.close();
                }
            } catch (IOException ignored) {
            }
            socket = null;
            outputStream = null;
        }
    }
}

logback.xml 文件

<configuration>
    <!-- 控制台 Appender -->
    <appender name="CONSOLE_1" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--
    <appender name="SYSLOG_APPENDER_1" class="ch.qos.logback.classic.net.SyslogAppender">
        <syslogHost>localhost</syslogHost>
        <port>514</port>
        <facility>LOCAL0</facility>
    </appender>
    -->

    <!-- 定义一个 SocketAppender -->
    <appender name="CUSTOM_SYSLOG_APPENDER_1" class="org.example.CustomSyslogAppender">
        <syslogHost>localhost</syslogHost>
        <port>514</port>
        <facility>LOCAL0</facility>
    </appender>

    <!-- 根日志器使用 CONSOLE Appender -->
    <root level="INFO">
        <appender-ref ref="CONSOLE_1" />
        <!--<appender-ref ref="SYSLOG_APPENDER_1" />-->
        <appender-ref ref="CUSTOM_SYSLOG_APPENDER_1" />
    </root>
</configuration>

启动类:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        org.slf4j.Logger logger = LoggerFactory.getLogger(Main.class);
        logger.info("Hello 1");
    }
}

3.结果

可以看到,日志写入到服务的 /var/log/syslog 文件

zxm@zxm-pc:~$ tail -n 1 /var/log/syslog
Oct 1 23:21:28 zxm-pc [main] org.example.Main Hello 1