package com.logentries.jul;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import static java.util.logging.ErrorManager.CLOSE_FAILURE;
import static java.util.logging.ErrorManager.FORMAT_FAILURE;
import static java.util.logging.ErrorManager.GENERIC_FAILURE;
import static java.util.logging.ErrorManager.OPEN_FAILURE;
import static java.util.logging.ErrorManager.WRITE_FAILURE;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;
/**
* <code>LogentriesHandler</code>: A handler for writing formatted records to a
* logentries.com. This handler uses the Token-based input.
*
* @author Björn Raupach (raupach@me.com)
*/
public final class LogentriesHandler extends Handler {
private String host;
private int port;
private byte[] token;
private boolean open;
private SocketChannel channel;
private ByteBuffer buffer;
private final byte[] newline = {0x0D, 0x0A};
private final byte space = 0x020;
public LogentriesHandler() {
configure();
connect();
buffer = ByteBuffer.allocate(4096);
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public byte[] getToken() {
return token;
}
public void setToken(byte[] token) {
this.token = token;
}
@Override
public synchronized void publish(LogRecord record) {
if (open && isLoggable(record)) {
String msg = formatMessage(record);
if (!msg.isEmpty()) {
boolean filled = fillAndFlip(msg);
if (filled) {
boolean drained = drain();
if (!drained) {
System.err.println("java.util.logging.ErrorManager: Sending to logentries.com failed. Trying to reconnect once.");
connect();
if (open) {
filled = fillAndFlip(msg);
if (filled) {
drained = drain();
if (!drained) {
System.err.println("java.util.logging.ErrorManager: Unable to reconnect. Shutting handler down.");
close();
}
}
}
}
}
}
}
}
String formatMessage(LogRecord record) {
String msg = "";
try {
msg = getFormatter().format(record);
// replace line separators with unicode equivalent
msg = msg.replace(System.getProperty("line.separator"), "\u2028");
} catch (Exception e) {
reportError("Error while formatting.", e, FORMAT_FAILURE);
}
return msg;
}
boolean fillAndFlip(String formattedMessage) {
try {
buffer.clear();
buffer.put(token);
buffer.put(space);
buffer.put(formattedMessage.getBytes(Charset.forName("UTF-8")));
buffer.put(newline);
} catch (BufferOverflowException e) {
reportError("Buffer exceeds capacity", e, WRITE_FAILURE);
return false;
}
buffer.flip();
return true;
}
boolean drain() {
while (buffer.hasRemaining()) {
try {
channel.write(buffer);
} catch (Exception e) {
reportError("Error while writing channel.", e, WRITE_FAILURE);
return false;
}
}
return true;
}
void configure() {
String cname = getClass().getName();
setLevel(getLevelProperty(cname + ".level", Level.INFO));
setFormatter(getFormatterProperty(cname + ".formatter", new SimpleFormatter()));
setHost(getStringProperty(cname + ".host", "data.logentries.com"));
setPort(getIntProperty(cname + ".port", 514));
setToken(getBytesProperty(cname + ".token", ""));
}
void connect() {
try {
channel = SocketChannel.open();
channel.connect(new InetSocketAddress(host, port));
open = true;
} catch (IOException e) {
open = false;
reportError(MessageFormat.format("Error connection to host: {0}:{1}", host, port), e, OPEN_FAILURE);
}
}
@Override
public void flush() {}
@Override
public void close() throws SecurityException {
open = false;
buffer = null;
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
reportError("Error while closing channel.", e, CLOSE_FAILURE);
}
}
}
// -- These methods are private in LogManager
Level getLevelProperty(String name, Level defaultValue) {
LogManager manager = LogManager.getLogManager();
String val = manager.getProperty(name);
if (val == null) {
return defaultValue;
}
Level l = Level.parse(val.trim());
return l != null ? l : defaultValue;
}
Formatter getFormatterProperty(String name, Formatter defaultValue) {
LogManager manager = LogManager.getLogManager();
String val = manager.getProperty(name);
try {
if (val != null) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> clz = cl.loadClass(val);
return (Formatter) clz.newInstance();
}
} catch (ClassNotFoundException e) {
reportError(MessageFormat.format("Error reading property ''{0}''", name), e, GENERIC_FAILURE);
} catch (InstantiationException e) {
reportError(MessageFormat.format("Error reading property ''{0}''", name), e, GENERIC_FAILURE);
} catch (IllegalAccessException e) {
reportError(MessageFormat.format("Error reading property ''{0}''", name), e, GENERIC_FAILURE);
}
return defaultValue;
}
String getStringProperty(String name, String defaultValue) {
LogManager manager = LogManager.getLogManager();
String val = manager.getProperty(name);
if (val == null) {
return defaultValue;
}
return val.trim();
}
byte[] getBytesProperty(String name, String defaultValue) {
return getStringProperty(name, defaultValue).getBytes();
}
int getIntProperty(String name, int defaultValue) {
LogManager manager = LogManager.getLogManager();
String val = manager.getProperty(name);
if (val == null) {
return defaultValue;
}
try {
return Integer.parseInt(val.trim());
} catch (NumberFormatException e) {
reportError(MessageFormat.format("Error reading property ''{0}''", name), e, GENERIC_FAILURE);
return defaultValue;
}
}
}