/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.flume.handlers.syslog;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.SourceFactory.SourceBuilder;
import com.cloudera.flume.core.Event;
import com.cloudera.flume.core.EventSource;
import com.cloudera.flume.handlers.text.EventExtractException;
/**
* This source listens for multiple tcp-based syslog data streams. This works
* for many concurrent connections, but may run into scaling problems.
*
* syslog entries sent over tcp are simply delimited by '\n' characters and are
* otherwise identical in format to udp-based syslog data.
*
* TODO (jon) setup a limit on the number of threads, find out how to modify
* filehandle/socket limits on windows/linux
*
* TODO(jon) Either do an nio/asynchronous version,
*
* TODO (jon) or make separate queues for each thread -- this may cause lock
* contention
*/
public class SyslogTcpSourceThreads extends EventSource.Base {
static final Logger LOG =
LoggerFactory.getLogger(SyslogTcpSourceThreads.class);
final public static int SYSLOG_TCP_PORT = 514;
final int port;
final LinkedBlockingQueue<Event> eventsQ = new LinkedBlockingQueue<Event>();
final List<ReaderThread> readers =
Collections.synchronizedList(new ArrayList<ReaderThread>());
final AtomicLong rejects = new AtomicLong();
volatile boolean closed = true;
public SyslogTcpSourceThreads(int port) {
this.port = port;
}
public SyslogTcpSourceThreads() {
this(SYSLOG_TCP_PORT); // this is syslog-ng's default tcp port.
}
// must synchronize this variable
ServerThread svrthread;
ServerSocket sock = null;
Object sockLock = new Object();
/**
* This thread just waits to accept incoming connections and spawn a reader
* thread.
*/
class ServerThread extends Thread {
final int port;
ServerThread(int port) {
this.port = port;
}
@Override
public void run() {
while (!closed) {
ServerSocket mySock = null; // guarantee no NPE at accept
synchronized (sockLock) {
mySock = sock; // get a local reference to sock.
}
if (mySock == null || mySock.isClosed())
return;
try {
Socket client = mySock.accept();
new ReaderThread(client).start();
} catch (IOException e) {
if (!closed) {
// could be IOException where we run out of file/socket handles.
LOG.error("accept had a problem", e);
}
return;
}
}
}
};
/**
* This thread takes a accepted socket and pull data out until it is empty.
*/
class ReaderThread extends Thread {
Socket in;
ReaderThread(Socket sock) {
readers.add(this);
this.in = sock;
}
@Override
public void run() {
try {
// process this connection.
DataInputStream dis = new DataInputStream(in.getInputStream());
while (!closed) {
try {
Event e = SyslogWireExtractor.extractEvent(dis);
if (e == null)
break;
eventsQ.put(e);
} catch (EventExtractException ex) {
rejects.incrementAndGet();
}
}
// done.
in.close();
} catch (IOException e) {
LOG.error("IOException with SyslogTcpSources", e);
} catch (InterruptedException e1) {
LOG.error("put into Queue interupted" + e1);
} finally {
if (in != null && in.isConnected()) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
readers.remove(this);
}
}
}
@Override
public void close() throws IOException {
LOG.info("Closing " + this);
synchronized (sockLock) {
closed = true;
if (sock != null) {
sock.close();
sock = null;
}
}
// wait for all readers to close (This is not robust!)
if (readers.size() != 0) {
List<ReaderThread> rs = new ArrayList<ReaderThread>(readers); //
for (ReaderThread r : rs) {
try {
r.join();
} catch (InterruptedException e) {
LOG.error("Reader threads interrupted, but we are closing", e);
}
}
}
try {
if (svrthread != null) {
svrthread.join();
svrthread = null;
}
} catch (InterruptedException e) {
LOG.error("Reader threads interrupted, but we are closing", e);
}
};
@Override
public Event next() throws IOException {
Event e = null;
try {
while ((e = eventsQ.poll(1000, TimeUnit.MILLISECONDS)) == null && !closed) {
// Do nothing, just checking variables if nothing has arrived.
}
} catch (InterruptedException e1) {
LOG.error("Tcp source polling interrupted ", e1); // Fail by throwing exn
throw new IOException(e1);
}
updateEventProcessingStats(e);
return e;
}
@Override
public void open() throws IOException {
LOG.info("Opening " + this);
synchronized (sockLock) {
if (!closed) {
throw new IOException("Attempted to double open socket");
}
closed = false;
if (sock == null) {
// depending on number of connections, may need to increase backlog
// value (automatic server socket argument, default is 50)
try {
sock = new ServerSocket(port);
sock.setReuseAddress(true);
} catch (IOException e) {
throw new IOException("failed to create serversocket " + e);
}
}
}
svrthread = new ServerThread(port);
svrthread.start();
}
public static SourceBuilder builder() {
return new SourceBuilder() {
@Override
public EventSource build(String... argv) {
int port = SYSLOG_TCP_PORT; // default udp port, need root permissions
// for this.
if (argv.length > 1) {
throw new IllegalArgumentException("usage: syslogTcp([port no]) ");
}
if (argv.length == 1) {
port = Integer.parseInt(argv[0]);
}
return new SyslogTcpSourceThreads(port);
}
};
}
}