Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Logback GELF - zero dependencies Logback GELF appender library.
* Copyright (C) 2016 Oliver Siegmar
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package de.siegmar.logbackgelf;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.UUID;
class GelfUdpChunker {
/**
* Maximum number of chunks, as defined per GELF Format Specification.
*/
private static final int MAX_CHUNKS = 128;
/**
* GELF chunk header, as defined per GELF Format Specification.
*/
private static final byte[] CHUNKED_GELF_HEADER = new byte[] {0x1e, 0x0f};
/**
* Length of message ID field, as defined per GELF Format Specification.
*/
private static final int MESSAGE_ID_LENGTH = 8;
/**
* Length of sequence number field, as defined per GELF Format Specification.
*/
private static final int SEQ_COUNT_LENGTH = 2;
/**
* Sum of all header fields.
*/
private static final int HEADER_LENGTH =
CHUNKED_GELF_HEADER.length + MESSAGE_ID_LENGTH + SEQ_COUNT_LENGTH;
private static final int MIN_CHUNK_SIZE = HEADER_LENGTH + 1;
/**
* Default chunk size set to 508 bytes. This prevents IP packet fragmentation.
*
* Minimum MTU (576) - IP header (up to 60) - UDP header (8) = 508
*/
private static final int DEFAULT_CHUNK_SIZE = 508;
/**
* Maximum chunk size set to 65467 bytes.
*
* Maximum IP packet size (65535) - IP header (up to 60) - UDP header (8) = 65467
*/
private static final int MAX_CHUNK_SIZE = 65467;
private static final int MAX_CHUNK_PAYLOAD_SIZE = MAX_CHUNK_SIZE - HEADER_LENGTH;
/**
* hostname used together with a timestamp to generate message IDs.
*/
private final String hostname;
/**
* The maximum size used for the payload.
*/
private final int maxChunkPayloadSize;
GelfUdpChunker() {
this(null);
}
GelfUdpChunker(final Integer maxChunkSize) {
this.hostname = buildHostname();
if (maxChunkSize != null) {
if (maxChunkSize < MIN_CHUNK_SIZE) {
throw new IllegalArgumentException("Minimum chunk size is " + MIN_CHUNK_SIZE);
}
if (maxChunkSize > MAX_CHUNK_SIZE) {
throw new IllegalArgumentException("Maximum chunk size is " + MAX_CHUNK_SIZE);
}
}
final int mcs = maxChunkSize != null ? maxChunkSize : DEFAULT_CHUNK_SIZE;
this.maxChunkPayloadSize = mcs - HEADER_LENGTH;
}
private String buildHostname() {
try {
return InetUtil.getLocalHostName();
} catch (final UnknownHostException e) {
return UUID.randomUUID().toString();
}
}
private static ByteBuffer buildChunk(final byte[] messageId, final byte[] message,
final byte chunkCount, final byte chunkNo,
final int maxChunkPayloadSize) {
final int chunkPayloadSize =
Math.min(maxChunkPayloadSize, message.length - chunkNo * maxChunkPayloadSize);
final ByteBuffer byteBuffer = ByteBuffer.allocate(HEADER_LENGTH + chunkPayloadSize);
// Chunked GELF magic bytes 2 bytes
byteBuffer.put(CHUNKED_GELF_HEADER);
// Message ID 8 bytes
byteBuffer.put(messageId);
// Sequence number 1 byte
byteBuffer.put(chunkNo);
// Sequence count 1 byte
byteBuffer.put(chunkCount);
// message
byteBuffer.put(message, chunkNo * maxChunkPayloadSize, chunkPayloadSize);
byteBuffer.flip();
return byteBuffer;
}
private byte[] buildMessageId() {
final byte[] messageIdSource =
(hostname + System.nanoTime()).getBytes(StandardCharsets.UTF_8);
return Arrays.copyOf(md5(messageIdSource), MESSAGE_ID_LENGTH);
}
private static byte[] md5(final byte[] data) {
try {
return MessageDigest.getInstance("MD5").digest(data);
} catch (final NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
Iterable chunks(final byte[] message) {
return new Iterable() {
@Override
public Iterator iterator() {
return new ChunkIterator(message);
}
};
}
private final class ChunkIterator implements Iterator {
private final byte[] message;
private final int chunkSize;
private final byte chunkCount;
private final byte[] messageId;
private byte chunkIdx;
private ChunkIterator(final byte[] message) {
this.message = message;
int localChunkSize = maxChunkPayloadSize;
int localChunkCount = calcChunkCount(message, localChunkSize);
if (localChunkCount > MAX_CHUNKS) {
// Number of chunks would exceed maximum chunk limit - use a larger chunk size
// as a last resort.
localChunkSize = MAX_CHUNK_PAYLOAD_SIZE;
localChunkCount = calcChunkCount(message, localChunkSize);
}
if (localChunkCount > MAX_CHUNKS) {
throw new IllegalArgumentException("Message to big (" + message.length + " B)");
}
this.chunkSize = localChunkSize;
this.chunkCount = (byte) localChunkCount;
messageId = localChunkCount > 1 ? buildMessageId() : null;
}
private int calcChunkCount(final byte[] msg, final int cs) {
return (msg.length + cs - 1) / cs;
}
@Override
public boolean hasNext() {
return chunkIdx < chunkCount;
}
@Override
public ByteBuffer next() {
if (!hasNext()) {
throw new NoSuchElementException("All " + chunkCount + " chunks consumed");
}
if (chunkCount == 1) {
chunkIdx++;
return ByteBuffer.wrap(message);
}
return buildChunk(messageId, message, chunkCount, chunkIdx++, chunkSize);
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}