Skip to content
This repository has been archived by the owner on Jun 29, 2023. It is now read-only.

ID with less risk of collision #206

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.target>1.6</maven.compiler.target>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<maven.compiler.source>1.7</maven.compiler.source>
<logstash-gelf-release-version>1.13.0</logstash-gelf-release-version>

<github.site.upload.skip>true</github.site.upload.skip>
Expand Down Expand Up @@ -240,8 +240,8 @@
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java16</artifactId>
<version>1.1</version>
<artifactId>java17</artifactId>
<version>1.0</version>
</signature>
</configuration>
</execution>
Expand Down
51 changes: 38 additions & 13 deletions src/main/java/biz/paluch/logging/gelf/intern/GelfMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

Expand Down Expand Up @@ -87,7 +87,6 @@ public class GelfMessage {

private String version = GELF_VERSION;
private String host;
private byte[] hostBytes = NONE;
private String shortMessage;
private String fullMessage;
private long javaTimestamp;
Expand Down Expand Up @@ -377,14 +376,14 @@ public ByteBuffer toTCPBuffer(ByteBuffer buffer) {
protected ByteBuffer[] sliceDatagrams(ByteBuffer source, int datagrams, ByteBuffer target) {
int messageLength = source.limit();

int millis = getCurrentMillis();
long msgId = generateMsgId();

// Reuse length of datagrams array since this is supposed to be the correct number of datagrams
ByteBuffer[] slices = new ByteBuffer[datagrams];
for (int idx = 0; idx < datagrams; idx++) {

int start = target.position();
target.put(GELF_CHUNKED_ID).putInt(millis).put(hostBytes).put((byte) idx).put((byte) datagrams);
target.put(GELF_CHUNKED_ID).putLong(msgId).put((byte) idx).put((byte) datagrams);

int from = idx * maximumMessageSize;
int to = from + maximumMessageSize;
Expand All @@ -402,8 +401,41 @@ protected ByteBuffer[] sliceDatagrams(ByteBuffer source, int datagrams, ByteBuff
return slices;
}

public int getCurrentMillis() {
return (int) System.currentTimeMillis();
long generateMsgId() {
// Considerations about generating the message ID: The GELF documentation suggests to
// "[g]enerate [the id] from millisecond timestamp + hostname for example":
// https://docs.graylog.org/en/3.1/pages/gelf.html#chunking
//
// However, relying on current time in milliseconds on the same system will result in a high
// collision probability if lots of messages are generated quickly. Things will be even
// worse if multiple servers send to the same log server. Adding the hostname is not
// guaranteed to help, and if the hostname is the FQDN it is even unlikely to be unique at
// all.
//
// The GELF module used by Logstash uses the first eight bytes of an MD5 hash of the current
// time as floating point, a hyphen, and an eight byte random number:
// https://github.com/logstash-plugins/logstash-output-gelf
// https://github.com/graylog-labs/gelf-rb/blob/master/lib/gelf/notifier.rb#L239 It probably
// doesn't have to be that clever:
//
// Using the timestamp plus a random number will mean we only have to worry about collision
// of random numbers within the same milliseconds. How short can the timestamp be before it
// will collide with old timestamps? Every second Graylog will evict expired messaged (5
// seconds old) from the pool:
// https://github.com/Graylog2/graylog2-server/blob/master/graylog2-server/src/main/java/org/graylog2/inputs/codecs/GelfChunkAggregator.java
// Thus, we just need six seconds which will require 13 bits. Then we can spend the rest on
// a random number.

return (getRandomLong() & 0xFFFFFFFFFFFFE000L) |
(getCurrentTimeMillis() & 0x1FFFL);
}

long getRandomLong() {
return ThreadLocalRandom.current().nextLong();
}

long getCurrentTimeMillis() {
return System.currentTimeMillis();
}

private byte[] gzipMessage(byte[] message) {
Expand Down Expand Up @@ -454,9 +486,6 @@ public String getHost() {

public void setHost(String host) {
this.host = host;
if (host != null) {
this.hostBytes = lastFourAsciiBytes(host);
}
}

public String getShortMessage() {
Expand Down Expand Up @@ -604,9 +633,6 @@ public boolean equals(Object o) {
if (host != null ? !host.equals(that.host) : that.host != null) {
return false;
}
if (!Arrays.equals(hostBytes, that.hostBytes)) {
return false;
}
if (level != null ? !level.equals(that.level) : that.level != null) {
return false;
}
Expand All @@ -624,7 +650,6 @@ public boolean equals(Object o) {
public int hashCode() {
int result = version != null ? version.hashCode() : 0;
result = 31 * result + (host != null ? host.hashCode() : 0);
result = 31 * result + (hostBytes != null ? Arrays.hashCode(hostBytes) : 0);
result = 31 * result + (shortMessage != null ? shortMessage.hashCode() : 0);
result = 31 * result + (fullMessage != null ? fullMessage.hashCode() : 0);
result = 31 * result + (int) (javaTimestamp ^ (javaTimestamp >>> 32));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,23 @@ void testUdpChunked() throws Exception {
}
}

@Test
void testGenerateMsgId() {
GelfMessage gelfMessage = new GelfMessage() {
@Override
long getRandomLong() {
return 0x804020100804A201L;
}

@Override
long getCurrentTimeMillis() {
return 0x90C06030090C1683L;
}
};

assertThat(gelfMessage.generateMsgId()).isEqualTo(0x804020100804B683L);
}

String toString(ByteBuffer allocate) {
if (allocate.hasArray()) {
return new String(allocate.array(), 0, allocate.arrayOffset() + allocate.position());
Expand Down Expand Up @@ -249,8 +266,8 @@ private GelfMessage createGelfMessage() {

GelfMessage gelfMessage = new GelfMessage() {
@Override
public int getCurrentMillis() {
return 1000;
long generateMsgId() {
return 0x8040201008048683L;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ private GelfMessage createGelfMessage() {

GelfMessage gelfMessage = new GelfMessage() {
@Override
public int getCurrentMillis() {
return 1000;
long generateMsgId() {
return 0x8040201008048683L;
}
};

Expand All @@ -135,8 +135,8 @@ private PoolingGelfMessage createPooledGelfMessage() {

PoolingGelfMessage gelfMessage = new PoolingGelfMessage(PoolHolder.threadLocal()) {
@Override
public int getCurrentMillis() {
return 1000;
long generateMsgId() {
return 0x8040201008048683L;
}
};

Expand Down