-
-
Save pschichtel/cb5556b9d5b681e1e77afabb749186e9 to your computer and use it in GitHub Desktop.
A faster GelfEncoder implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ch.qos.logback.classic.pattern.ThrowableProxyConverter | |
import ch.qos.logback.classic.spi.ILoggingEvent | |
import ch.qos.logback.classic.util.LevelToSyslogSeverity | |
import ch.qos.logback.core.CoreConstants.LINE_SEPARATOR | |
import com.fasterxml.jackson.core.JsonGenerator | |
import com.fasterxml.jackson.databind.ObjectWriter | |
import com.fasterxml.jackson.databind.json.JsonMapper | |
import de.siegmar.logbackgelf.GelfEncoder | |
import java.io.ByteArrayOutputStream | |
import java.math.BigDecimal | |
import java.net.InetAddress | |
import java.net.UnknownHostException | |
import java.util.concurrent.atomic.AtomicInteger | |
private val version = "1.1".toByteArray() | |
private fun JsonGenerator.writeUtf8StringField(name: String, value: ByteArray) { | |
writeFieldName(name) | |
writeUTF8String(value, 0, value.size) | |
} | |
private fun JsonGenerator.writeStringField(name: String, value: CharArray) { | |
writeFieldName(name) | |
writeString(value, 0, value.size) | |
} | |
private fun serialize( | |
gen: JsonGenerator, | |
event: ILoggingEvent, | |
hostname: ByteArray, | |
stacktrace: String?, | |
staticFields: Array<Pair<String, ByteArray>>, | |
) { | |
val message = event.formattedMessage | |
gen.writeStartObject() | |
gen.writeUtf8StringField("version", version) | |
gen.writeUtf8StringField("host", hostname) | |
gen.writeStringField("short_message", message) | |
if (stacktrace != null) { | |
val fullMessage = CharArray(message.length + LINE_SEPARATOR.length + stacktrace.length) | |
message.toCharArray(fullMessage, destinationOffset = 0) | |
LINE_SEPARATOR.toCharArray(fullMessage, destinationOffset = message.length) | |
stacktrace.toCharArray(fullMessage, destinationOffset = message.length + LINE_SEPARATOR.length) | |
gen.writeStringField("full_message", fullMessage) | |
gen.writeStringField("_exception", stacktrace) | |
} else { | |
gen.writeStringField("full_message", message) | |
} | |
gen.writeNumberField("timestamp", BigDecimal(event.timeStamp).movePointLeft(3)) | |
gen.writeNumberField("level", LevelToSyslogSeverity.convert(event)) | |
gen.writeStringField("_logger_name", event.loggerName) | |
gen.writeStringField("_thread_name", event.threadName) | |
for ((key, value) in staticFields) { | |
gen.writeUtf8StringField(key, value) | |
} | |
gen.writeEndObject() | |
} | |
class FastGelfEncoder : GelfEncoder() { | |
private val writer: ObjectWriter = JsonMapper().writer() | |
private val tpc = ThrowableProxyConverter() | |
private lateinit var hostname: ByteArray | |
private var constantFields = emptyArray<Pair<String, ByteArray>>() | |
private val largestBuffer = AtomicInteger(500) | |
private fun getLocalHostName(): String { | |
return try { | |
val localHost = InetAddress.getLocalHost() | |
localHost.canonicalHostName.trim().ifEmpty { localHost.hostName } | |
} catch (e: UnknownHostException) { | |
addWarn("Could not determine local hostname", e) | |
"unknown" | |
} | |
} | |
override fun addStaticField(staticField: String) { | |
super.addStaticField(staticField) | |
val colonPos = staticField.indexOf(':') | |
if (colonPos == -1) { | |
addWarn("staticField must be in format key:value - rejecting '$staticField'") | |
return | |
} | |
val value = staticField.encodeToByteArray(colonPos + 1, staticField.length) | |
constantFields += Pair("_" + staticField.substring(0, colonPos), value) | |
} | |
override fun start() { | |
tpc.start() | |
hostname = getLocalHostName().toByteArray() | |
super.start() | |
} | |
override fun encode(event: ILoggingEvent): ByteArray { | |
val stacktrace = event.throwableProxy?.let { tpc.convert(event) } | |
val bufferSize = largestBuffer.get() | |
val outputBuffer = ByteArrayOutputStream(bufferSize) | |
writer.createGenerator(outputBuffer).use { generator -> | |
serialize(generator, event, hostname, stacktrace, constantFields) | |
} | |
val output = outputBuffer.toByteArray() | |
if (output.size > bufferSize) { | |
largestBuffer.compareAndSet(bufferSize, output.size) | |
} | |
return output | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment