/*
* Copyright 2009 NCHOVY
*
* Licensed 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.krakenapps.syslog.impl;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import org.krakenapps.syslog.Syslog;
import org.krakenapps.syslog.SyslogListener;
import org.krakenapps.syslog.SyslogProfile;
import org.krakenapps.syslog.SyslogServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SyslogReceiver implements SyslogServer, Runnable {
final Logger logger = LoggerFactory.getLogger(SyslogReceiver.class.getName());
private SyslogProfile profile;
private DatagramSocket socket;
private byte[] buffer;
private Charset charset;
private PushRunner internalRunner = new PushRunner();
private Thread thread;
private Thread pushRunnerThread;
private LinkedBlockingQueue<Syslog> packetQueue;
private Set<SyslogListener> callbacks;
private Date bootTime = new Date();
private AtomicLong counter = new AtomicLong();
private volatile boolean doStop = false;
private volatile boolean doStopPush = false;
public SyslogReceiver(SyslogProfile profile) {
this.profile = profile;
this.charset = Charset.forName(profile.getCharset());
this.packetQueue = new LinkedBlockingQueue<Syslog>(profile.getQueueSize());
this.callbacks = Collections.newSetFromMap(new ConcurrentHashMap<SyslogListener, Boolean>());
this.thread = new Thread(this, "Syslog " + profile.getListenAddress());
this.pushRunnerThread = new Thread(internalRunner, "Syslog Push " + profile.getListenAddress());
}
@Override
public InetSocketAddress getListenAddress() {
return new InetSocketAddress(profile.getAddress(), profile.getPort());
}
@Override
public Charset getCharset() {
return Charset.forName(profile.getCharset());
}
public void open() throws SocketException {
if (socket != null)
throw new IllegalStateException("already opened");
logger.info("kraken syslog: opening syslog server [{}]", profile);
socket = new DatagramSocket(getListenAddress());
socket.setSoTimeout(500);
bootTime = new Date();
buffer = new byte[1024];
doStop = false;
doStopPush = false;
pushRunnerThread.start();
thread.start();
}
public void close() {
if (socket == null)
return;
doStop = true;
doStopPush = true;
socket.close();
try {
pushRunnerThread.interrupt();
pushRunnerThread.join(2500);
thread.join(2500);
} catch (InterruptedException e) {
logger.warn("kraken syslog: internal runner didn't respond for stop request");
}
logger.info("kraken syslog: closed server [{}]", profile);
}
private class PushRunner implements Runnable {
public void run() {
try {
while (!doStopPush) {
try {
Syslog syslog = packetQueue.take();
counter.incrementAndGet();
// dispatch syslog
for (SyslogListener callback : callbacks) {
try {
callback.onReceive(syslog);
} catch (Exception e) {
logger.warn("kraken syslog: syslog callback should not throw any exception", e);
}
}
} catch (InterruptedException e) {
if (doStop) {
logger.info("kraken syslog: internal runner interrupted.");
break;
} else
logger.info("kraken syslog: internal runner interrupted, but continue waiting");
} catch (Exception e) {
logger.warn("kraken syslog: internal runner error, {}, {}", e.getClass().getName(), e.getMessage());
if (logger.isDebugEnabled())
logger.debug("kraken syslog: exception details", e);
}
}
} finally {
logger.info("kraken syslog: server [{}] stopped", profile.getName());
doStopPush = false;
}
}
}
@Override
public void run() {
try {
while (!doStop) {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try {
socket.receive(packet);
String text = new String(packet.getData(), 0, packet.getLength(), charset);
int facility = -1;
int severity = -1;
int brace = text.indexOf('>');
if (text.charAt(0) == '<' && brace > 0 && brace <= 5) {
int pri = Integer.valueOf(text.substring(1, brace));
facility = pri / 8;
severity = pri % 8;
text = text.substring(brace + 1);
}
InetSocketAddress remote = new InetSocketAddress(packet.getAddress(), packet.getPort());
Syslog syslog = new Syslog(new Date(), remote, facility, severity, text);
syslog.setLocalAddress((InetSocketAddress) packet.getSocketAddress());
packetQueue.put(syslog);
} catch (SocketTimeoutException e) {
} catch (UnsupportedEncodingException e) {
logger.warn("kraken syslog: unsupported encoding detected", e);
} catch (Throwable t) {
logger.warn("kraken syslog: receive error", t);
}
}
} finally {
doStop = false;
}
}
public int getQueueSize() {
return packetQueue.size();
}
@Override
public void addListener(SyslogListener callback) {
if (callback == null)
throw new IllegalArgumentException("syslog listener must not be null");
callbacks.add(callback);
}
@Override
public void removeListener(SyslogListener callback) {
if (callback == null)
throw new IllegalArgumentException("syslog listener must not be null");
callbacks.remove(callback);
}
@Override
public String toString() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String since = dateFormat.format(bootTime);
int pending = packetQueue.size();
return profile.toString() + ", since=" + since + ", received=" + counter.get() + ", pending=" + pending;
}
}