/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.log4j.helpers;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* TCPSyslogWriter mimics {@link SyslogWriter} behavior,
* but sends each peace of data (normally, a String) over a TCP socket.
* The socket is opened and closed on each write.
*
* @since 1.2.15-tcp-patch
* @author Daniil V. Kolpakov (ctxm.com)
*/
public class TCPSyslogWriter extends Writer {
static final int SYSLOG_TCP_PORT = 514;
private transient final String host;
private transient final int port;
private final Socket socket;
private final OutputStream out;
/**
* Regular expression pattern to match "tcp:host:port" string, with
* capturing groups for host and port parts. The host can be surrounded by
* a pair of square brackets, and the tcp: part is optional
*/
final static Pattern hostPortPattern =
Pattern.compile("(?:tcp:)? # optional tcp: prefix\n"
+ "(?: # \n"
+ "\\[ # opening bracket\n"
+ "([^\\]]+) # IPv6 host: any chars except. ']'\n"
+ "] # closing bracket\n"
+ "| # \n"
+ "([^:]+)) # IPv4 host or domain name\n"
+ "(?: # optional part start\n"
+ ": # colon\n"
+ "(\\d+) # port (digits)\n"
+ ")? # optional part end",
Pattern.COMMENTS);
/**
* Creates new writer based on the tcp:host:port specification. The ":port"
* part is optional; the "tcp:" prefix is optional too; the port is decimal.
* If you need to specify IPv6 address, enclose it in square brackets.
*
* @param hostPort the host:port specification
*/
public TCPSyslogWriter(String hostPort) {
final Matcher m = hostPortPattern.matcher(hostPort);
Socket tempSocket = null;
OutputStream tempOut = null;
if (m.matches()) {
if (m.group(1) == null) {
this.host = m.group(2); // IPv4 or domain name
} else {
this.host = m.group(1); //IPv6
}
if (m.group(3) == null) {
this.port = SYSLOG_TCP_PORT;
} else {
this.port = Integer.parseInt(m.group(3));
}
try {
tempSocket = new Socket(host, port);
tempSocket.setTcpNoDelay(false);
tempOut = tempSocket.getOutputStream();
}
catch (Exception e) {
e.printStackTrace();
LogLog.error("Could not instantiate DatagramSocket to " + host +
". All logging will FAIL.", e);
}
} else {
LogLog.error("Could not parse host:port parameter (" + hostPort
+ "). All logging will FAIL.");
this.host = null;
this.port = -1;
}
socket = tempSocket;
out = tempOut;
}
/**
* Close the socket opened by the constructor if it is not null
*/
public void close() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
/**
* No-op, since output is not buffered
*/
public void flush() {
}
/**
* Sends the {@code data} specified if the connection exists
*/
public void write(final String data) throws IOException {
if (socket != null) {
out.write(data.getBytes());
}
}
/**
* Opens TCP socket connection, sends the {@code count} chars out of the
* {@code data} parameter starting with {@code start} and closes the
* connection.
*/
public void write(final char[] data,
final int start,
final int count) throws IOException {
write(new String(data, start, count));
}
public void finalize() {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
}