package biz.paluch.logging.gelf.netty; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.zip.GZIPInputStream; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.socket.DatagramPacket; import io.netty.util.ReferenceCountUtil; /** * @author Mark Paluch * @since 10.11.13 10:35 */ public class GelfInboundHandler extends ChannelInboundHandlerAdapter { private static final byte[] GELF_CHUNKED_ID = new byte[] { 0x1e, 0x0f }; private static final byte[] GZIP_ID = new byte[] { 0x1f, 0xffffff8b }; private final Map<ChunkId, List<Chunk>> chunks = new HashMap<ChunkId, List<Chunk>>(); private final List<Object> values = new ArrayList<Object>(); private ByteArrayOutputStream intermediate; private final ObjectMapper objectMapper = new ObjectMapper(); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { boolean requireNullEnd = false; ByteBuf buffer = null; if (msg instanceof DatagramPacket) { DatagramPacket packet = (DatagramPacket) msg; buffer = packet.content(); } else if (msg instanceof ByteBuf) { buffer = (ByteBuf) msg; requireNullEnd = true; } ByteArrayOutputStream temporaryBuffer = new ByteArrayOutputStream(); while (buffer.readableBytes() != 0) { buffer.readBytes(temporaryBuffer, buffer.readableBytes()); } byte bytes[] = temporaryBuffer.toByteArray(); if (bytes.length > 2) { // if chunked 0x1e, 0x0f ,8 bytes id, number, size // if gzip 0x1f 0x8b if (startsWith(bytes, GELF_CHUNKED_ID)) { byte id[] = new byte[8]; System.arraycopy(bytes, 2, id, 0, 8); byte number = bytes[10]; byte count = bytes[11]; ChunkId chunkId = new ChunkId(id, count); byte message[] = new byte[bytes.length - 12]; System.arraycopy(bytes, 12, message, 0, message.length); synchronized (chunks) { List<Chunk> messageChunks; if (chunks.containsKey(chunkId)) { messageChunks = chunks.get(chunkId); } else { messageChunks = new ArrayList<Chunk>(); chunks.put(chunkId, messageChunks); } messageChunks.add(new Chunk(message, number)); if (messageChunks.size() == count) { Collections.sort(messageChunks); bytes = getBytes(messageChunks); chunks.remove(chunkId); } else { return; } } } if (requireNullEnd) { if (bytes[bytes.length - 1] != 0) { intermediate = new ByteArrayOutputStream(); intermediate.write(bytes, 0, bytes.length); return; } else if (intermediate != null) { intermediate.write(bytes, 0, bytes.length); bytes = intermediate.toByteArray(); intermediate = null; } } InputStream is = null; if (startsWith(bytes, GZIP_ID)) { is = new GZIPInputStream(new ByteArrayInputStream(bytes)); } else { if (bytes[bytes.length - 1] == 0) { is = new ByteArrayInputStream(bytes, 0, bytes.length - 1); } else { is = new ByteArrayInputStream(bytes); } } Object parse = objectMapper.readValue(is, Map.class); synchronized (values) { values.add(parse); } } } finally { ReferenceCountUtil.release(msg); } } private byte[] getBytes(List<Chunk> messageChunks) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); for (Chunk messageChunk : messageChunks) { buffer.write(messageChunk.bytes); } return buffer.toByteArray(); } private boolean startsWith(byte[] base, byte[] compareWith) { if (base.length >= compareWith.length) { for (int i = 0; i < compareWith.length; i++) { if (base[i] != compareWith[i]) { return false; } } return true; } return false; } public List<Object> getJsonValues() { synchronized (values) { return new ArrayList<Object>(values); } } public void clear() { synchronized (values) { values.clear(); chunks.clear(); } } private class Chunk implements Comparable<Chunk> { private byte[] bytes; private int seq; private Chunk(byte[] bytes, int seq) { this.bytes = bytes; this.seq = seq; } @Override public int compareTo(Chunk o) { return seq - o.seq; } } private class ChunkId { private byte[] id; private byte count; private ChunkId(byte[] id, byte count) { this.id = id; this.count = count; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof ChunkId)) { return false; } ChunkId chunkId = (ChunkId) o; if (count != chunkId.count) { return false; } if (!Arrays.equals(id, chunkId.id)) { return false; } return true; } @Override public int hashCode() { int result = id != null ? Arrays.hashCode(id) : 0; result = 31 * result + (int) count; return result; } } }