Skip to content

Instantly share code, notes, and snippets.

@pschichtel
Last active February 15, 2023 08:02
Show Gist options
  • Save pschichtel/cb5556b9d5b681e1e77afabb749186e9 to your computer and use it in GitHub Desktop.
Save pschichtel/cb5556b9d5b681e1e77afabb749186e9 to your computer and use it in GitHub Desktop.
A faster GelfEncoder implementation
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