#java #spring #spring-boot #kotlin #spring-cloud-sleuth
Вопрос:
Я реализовал пользовательский механизм распространения, так как хочу использовать для трассировки другие имена заголовков(не x-b3-traceid).
После этого я решил использовать багаж для распространения некоторых дополнительных полей.
Из коробки spring использует составное распространение, трассировка начинается с распространения пакетов, после делегирования в B3Propagation. Итак, как я могу повторить это поведение, но вместо использования B3Propagation использовать мою собственную реализацию?
Вот моя реализация пропагатора
package com.example.demo
import brave.internal.Platform
import brave.internal.codec.HexCodec
import brave.internal.propagation.StringPropagationAdapter
import brave.propagation.Propagation
import brave.propagation.TraceContext
import brave.propagation.TraceContextOrSamplingFlags
import org.springframework.stereotype.Component
@Component
class CustomPropagator :
Propagation.Factory(),
Propagation<String> {
override fun keys(): List<String> {
return HEADER_KEYS
}
override fun <R> injector(setter: Propagation.Setter<R, String>): TraceContext.Injector<R> {
return TraceContext.Injector { traceContext: TraceContext, request: R ->
setter.put(request, ApplicationHeaders.TRACE_ID, traceContext.traceIdString())
setter.put(request, ApplicationHeaders.SPAN_ID, traceContext.spanIdString())
val parentId: String = traceContext.parentIdString()
if (!parentId.isEmpty()) setter.put(request, B3_PARENT_SPAN_ID, parentId)
if (traceContext.debug()) {
setter.put(request, B3_FLAGS, "1")
} else if (traceContext.sampled() != null) {
setter.put(request, B3_SAMPLED, if (traceContext.sampled()) "1" else "0")
}
}
}
override fun <R> extractor(getter: Propagation.Getter<R, String>): TraceContext.Extractor<R> {
return TraceContext.Extractor { request: R ->
if (request == null) throw NullPointerException("request == null")
val sampled = getter[request, B3_SAMPLED]
val sampledV: Boolean?
sampledV = if (sampled == null) {
null // defer decision
} else if (sampled.length == 1) { // handle fast valid paths
val sampledC = sampled[0]
if (sampledC == '1') {
true
} else if (sampledC == '0') {
false
} else {
// todo
Platform.get().log(B3_SAMPLED_MALFORMED, sampled, null)
return@Extractor TraceContextOrSamplingFlags.EMPTY // trace context is malformed so return empty
}
} else if (sampled.equals("true", ignoreCase = true)) { // old clients
true
} else if (sampled.equals("false", ignoreCase = true)) { // old clients
false
} else {
Platform.get().log(B3_SAMPLED_MALFORMED, sampled, null)
return@Extractor TraceContextOrSamplingFlags.EMPTY // Restart trace instead of propagating false
}
// The only flag we action is 1, but it could be that any integer is present.
// Here, we leniently parse as debug is not a primary consideration of the trace context.
val debug = "1" == getter[request, B3_FLAGS]
val traceIdString = getter[request, B3_TRACE_ID] ?: getter[request, ApplicationHeaders.TRACE_ID]
val spanIdString = getter[request, B3_SPAN_ID] ?: getter[request, ApplicationHeaders.SPAN_ID]
// It is ok to go without a trace ID, if sampling or debug is set
if (traceIdString == null) {
if (debug) return@Extractor TraceContextOrSamplingFlags.DEBUG
if (sampledV != null) {
return@Extractor if (sampledV) TraceContextOrSamplingFlags.SAMPLED else TraceContextOrSamplingFlags.NOT_SAMPLED
}
}
// Try to parse the trace IDs into the context
val traceContextBuilder = TraceContext.newBuilder()
val traceIdFilled = traceContextBuilder.fillTraceId(traceIdString, "trace id")
val spanIdFilled = traceContextBuilder.fillSpanId(spanIdString, "span id")
val parentIdFilled = traceContextBuilder.fillParentId(getter, request, "parent id")
if (traceIdFilled amp;amp; spanIdFilled amp;amp; parentIdFilled) {
if (sampledV != null) traceContextBuilder.sampled(sampledV)
if (debug) traceContextBuilder.debug(true)
return@Extractor TraceContextOrSamplingFlags.create(traceContextBuilder.build())
}
return@Extractor TraceContextOrSamplingFlags.EMPTY
}
}
override fun <K> create(keyFactory: Propagation.KeyFactory<K>): Propagation<K> {
return StringPropagationAdapter.create(this, keyFactory)
}
companion object {
const val B3_TRACE_ID = "X-B3-TraceId"
const val B3_SPAN_ID = "X-B3-SpanId"
const val B3_PARENT_SPAN_ID = "X-B3-ParentSpanId"
const val B3_SAMPLED = "X-B3-Sampled"
const val B3_SAMPLED_MALFORMED = "Invalid input: expected 0 or 1 for $B3_SAMPLED, but found '{0}'"
const val B3_FLAGS = "X-B3-Flags"
val HEADER_KEYS = listOf<String>(
B3_TRACE_ID,
B3_SPAN_ID,
B3_PARENT_SPAN_ID,
B3_SAMPLED,
B3_FLAGS,
"X-trace-id"
"X-span-id"
)
}
}
private fun <R> TraceContext.Builder.fillParentId(
getter: Propagation.Getter<R, String>,
request: R,
key: String
): Boolean {
val parentIdString = getter[request, key] ?: return true
// absent parent is ok
val length = parentIdString.length
if (isIdLengthInvalid(key, length, 16)) return false
val parentIdLong = HexCodec.lenientLowerHexToUnsignedLong(parentIdString, 0, length)
parentId(parentIdLong)
if (parentIdLong != 0L) return true
Platform.get().log("{0} is not a lower-hex string", parentIdString, null)
return false
}
private fun TraceContext.Builder.fillSpanId(spanIdString: String?, key: String): Boolean {
if (spanIdString == null) return false
val length = spanIdString.length
if (isIdLengthInvalid(key, length, 16)) return false
val spanIdLong = HexCodec.lenientLowerHexToUnsignedLong(spanIdString, 0, length)
spanId(spanIdLong)
if (spanIdLong == 0L) {
if (isAllZeros(spanIdString, 0, length)) {
Platform.get().log("Invalid input: spanId was all zeros", null)
return false
}
Platform.get().log("{0} is not a lower-hex string", spanIdString, null)
return false
}
return true
}
private fun TraceContext.Builder.fillTraceId(traceIdString: String?, key: String): Boolean {
if (traceIdString == null) return false
val length = traceIdString.length
if (isIdLengthInvalid(key, length, 32)) return false
var traceIdHighAllZeros = false
var traceIdAllZeros = false
// left-most characters, if any, are the high bits
val traceIdIndex = Math.max(0, length - 16)
var traceIdHigh = 0L
if (traceIdIndex > 0) {
traceIdHigh = HexCodec.lenientLowerHexToUnsignedLong(traceIdString, 0, traceIdIndex)
traceIdHigh(HexCodec.lenientLowerHexToUnsignedLong(traceIdString, 0, traceIdIndex))
if (traceIdHigh == 0L) {
traceIdHighAllZeros = isAllZeros(traceIdString, 0, traceIdIndex)
if (!traceIdHighAllZeros) {
Platform.get().log("{0} is not a lower-hex string", traceIdString, null)
return false
}
}
} else {
traceIdHighAllZeros = true
}
// right-most up to 16 characters are the low bits
val traceIdLong = HexCodec.lenientLowerHexToUnsignedLong(traceIdString, traceIdIndex, length)
traceId(HexCodec.lenientLowerHexToUnsignedLong(traceIdString, traceIdIndex, length))
if (traceIdLong == 0L) {
traceIdAllZeros = isAllZeros(traceIdString, traceIdIndex, length)
if (!traceIdAllZeros) {
Platform.get().log("{0} is not a lower-hex string", traceIdString, null)
return false
}
}
if (traceIdHighAllZeros amp;amp; traceIdAllZeros) {
Platform.get().log("Invalid input: traceId was all zeros", null)
}
return traceIdHigh != 0L || traceIdLong != 0L
}
fun isIdLengthInvalid(key: String, length: Int, max: Int): Boolean {
if (length > 1 amp;amp; length <= max) return false
Platform.get().log(
if (max == 32) "{0} should be a 1 to 32 character lower-hex string with no prefix" else "{0} should be a 1 to 16 character lower-hex string with no prefix",
key,
null
)
return true
}
fun isAllZeros(value: String, beginIndex: Int, endIndex: Int): Boolean {
for (i in beginIndex until endIndex) {
if (value[i] != '0') return false
}
return true
}
Вот свойства:
spring.sleuth.baggage.remote-fields=kek
spring.sleuth.baggage.correlation-fields=kek
spring.sleuth.baggage.tag-fields=kek
spring.sleuth.propagation.type=custom
Вот шаблон обратного входа
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%X{traceId:-}/%X{spanId:-}] [%X{kek:-}] %highlight(%-5level) %cyan(%-40.40logger{39}) : %message %n%whitespace_exception</pattern>
Когда я комментирую CustomPropagator, он работает так, как ожидалось, значение заголовка «kek» отображается в журналах.
Но когда CustomPropagator в контексте, в журналах отображаются только traceid и spanid
О версиях:
<spring-cloud.version>2020.0.3</spring-cloud.version>
<spring-boot-starter-parent.version>2.4.5</spring-boot-starter-parent.version>
Комментарии:
1. Я думаю, что то, что вы делаете, слишком сложно, чтобы устранить это, я бы начал с реализации, которую предлагают документы: docs.spring.io/spring-cloud-sleuth/docs/current/reference/… После того, как вы сделаете это возможным, вы можете добавить больше логики.