Распространение багажа Spring cloud sleuth не работает, когда реализован пользовательский механизм распространения

#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/… После того, как вы сделаете это возможным, вы можете добавить больше логики.