Copilot commented on code in PR #7705:
URL: https://github.com/apache/shenyu/pull/7705#discussion_r4301683170
##########
shenyu-plugin/shenyu-plugin-tracing/src/main/java/org/apache/shenyu/plugin/tracing/TracingPlugin.java:
##########
@@ -103,6 +123,15 @@ public String named() {
*/
private Mono<Void> withSpan(final ServerWebExchange exchange, final
Mono<Void> chainResult) {
final Context parent = extractor.extract(exchange);
+ if (!createSpan) {
+ // the deployment instruments the gateway already, so the request
is propagated without
+ // creating a second span for it: the incoming context is the
current context, which the
+ // access log and the outgoing call see as well.
+ ReactorTracingContext.store(exchange, parent);
+ exposeTraceId(exchange, Span.fromContext(parent));
+ return chainResult
+ .contextWrite(context ->
context.put(TracingConstants.REACTOR_CONTEXT_KEY, parent));
Review Comment:
With `createSpan=false`, this stores the inbound client's context rather
than the agent's active server span. `TraceContextExtractor` extracts from
`Context.root()`: without `traceparent`, this plugin exposes no access-log
trace ID and supplies no valid context to its outbound propagator; with
headers, it propagates the remote parent instead of the agent's gateway span.
Prefer the active instrumentation context, preserving it across Reactor
subscriptions, and fall back to header extraction only when no valid active
span exists. Add coexistence tests with an active span, both with and without
incoming headers.
##########
shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-tracing/src/main/java/org/apache/shenyu/springboot/starter/plugin/tracing/TracingPluginConfiguration.java:
##########
@@ -93,17 +141,213 @@ public Tracer
tracingTracer(@Qualifier("tracingTracerProvider") final SdkTracerP
* Tracing plugin.
*
* @param tracer the tracer
+ * @param shenyuConfig the shenyu config
* @return the shenyu plugin
*/
@Bean
- public ShenyuPlugin tracingPlugin(@Qualifier("tracingTracer") final Tracer
tracer) {
- return new TracingPlugin(tracer);
+ public ShenyuPlugin tracingPlugin(@Qualifier("tracingTracer") final Tracer
tracer,
+ final ShenyuConfig shenyuConfig) {
+ return new TracingPlugin(tracer, new TraceContextExtractor(),
createSpan(shenyuConfig));
}
+ /**
+ * Acquires the tracing config.
+ *
+ * @param shenyuConfig the shenyu config
+ * @return the tracing config, empty while it is not configured
+ */
+ private Optional<ShenyuConfig.TracingConfig> tracingConfig(final
ShenyuConfig shenyuConfig) {
+ return Optional.ofNullable(shenyuConfig.getTracing());
+ }
+
+ /**
+ * Acquires the service name of the gateway.
+ *
+ * @param shenyuConfig the shenyu config
+ * @return the service name
+ */
private String serviceName(final ShenyuConfig shenyuConfig) {
- return Optional.ofNullable(shenyuConfig.getTracing())
+ return tracingConfig(shenyuConfig)
.map(ShenyuConfig.TracingConfig::getServiceName)
.filter(StringUtils::isNotBlank)
.orElse(DEFAULT_SERVICE_NAME);
}
+
+ /**
+ * Tells whether the gateway creates a span of its own.
+ *
+ * @param shenyuConfig the shenyu config
+ * @return true while the plugin creates a span
+ */
+ private boolean createSpan(final ShenyuConfig shenyuConfig) {
+ return
tracingConfig(shenyuConfig).map(ShenyuConfig.TracingConfig::getCreateSpan).orElse(Boolean.TRUE);
+ }
+
+ /**
+ * Acquires the resource attributes which are added to the resource of the
gateway spans.
+ *
+ * @param shenyuConfig the shenyu config
+ * @return the configured resource attributes, empty while none is
configured
+ */
+ private Optional<Properties> resourceAttributes(final ShenyuConfig
shenyuConfig) {
+ return tracingConfig(shenyuConfig)
+ .map(ShenyuConfig.TracingConfig::getResourceAttributes)
+ .filter(attributes -> !attributes.isEmpty());
+ }
+
+ /**
+ * Builds the sampler which decides whether a request is traced.
+ *
+ * @param shenyuConfig the shenyu config
+ * @return the sampler
+ */
+ private Sampler sampler(final ShenyuConfig shenyuConfig) {
+ final ShenyuConfig.TracingConfig config = tracingConfig(shenyuConfig)
+ .orElseGet(ShenyuConfig.TracingConfig::new);
+ final String sampler = Optional.ofNullable(config.getSampler())
+ .map(String::trim)
+ .filter(StringUtils::isNotBlank)
+ .orElse(TracingConstants.SAMPLER_PARENT_BASED)
+ .toLowerCase(Locale.ROOT);
+ switch (sampler) {
+ case TracingConstants.SAMPLER_ALWAYS_ON:
+ return Sampler.alwaysOn();
+ case TracingConstants.SAMPLER_ALWAYS_OFF:
+ return Sampler.alwaysOff();
+ case TracingConstants.SAMPLER_RATIO:
+ return
Sampler.parentBased(Sampler.traceIdRatioBased(ratio(config.getSampleRatio())));
+ case TracingConstants.SAMPLER_PARENT_BASED:
+ return Sampler.parentBased(Sampler.alwaysOn());
+ default:
+ LOG.warn("shenyu tracing does not know the sampler {}, the
parent based sampler is used", sampler);
+ return Sampler.parentBased(Sampler.alwaysOn());
+ }
+ }
+
+ /**
+ * Normalizes the sample ratio.
+ *
+ * <p>A ratio which is not a number or which is out of the range would
silently change the
+ * sampling behaviour, so it is reported and clamped instead of failing
the gateway.</p>
+ *
+ * @param sampleRatio the configured sample ratio
+ * @return the ratio within the valid range
+ */
+ private double ratio(final double sampleRatio) {
+ if (Double.isNaN(sampleRatio)) {
+ LOG.warn("shenyu tracing could not read the sample ratio, no
request is sampled");
+ return 0.0D;
+ }
+ final double bounded = Math.max(0.0D, Math.min(1.0D, sampleRatio));
+ if (bounded != sampleRatio) {
+ LOG.warn("shenyu tracing clamps the sample ratio {} to {}",
sampleRatio, bounded);
+ }
+ return bounded;
+ }
+
+ /**
+ * Builds the batch processor of the exporter.
+ *
+ * @param exporter the exporter of the gateway spans
+ * @return the batch processor, empty while it cannot be created
+ */
+ private Optional<SpanProcessor> spanProcessor(final SpanExporter exporter)
{
+ try {
+ return Optional.of(BatchSpanProcessor.builder(exporter).build());
+ } catch (RuntimeException | LinkageError ex) {
+ LOG.warn("shenyu tracing could not create the batch processor of
the exporter, the spans are not exported", ex);
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Creates the OTLP exporter of the gateway spans.
+ *
+ * @param shenyuConfig the shenyu config
+ * @return the span exporter, empty while no endpoint is configured or the
exporter cannot be created
+ */
+ private Optional<SpanExporter> createSpanExporter(final ShenyuConfig
shenyuConfig) {
+ final Optional<ShenyuConfig.TracingConfig.OtlpConfig> configured =
tracingConfig(shenyuConfig)
+ .map(ShenyuConfig.TracingConfig::getOtlp)
+ .filter(otlp -> StringUtils.isNotBlank(otlp.getEndpoint()));
+ if (!configured.isPresent()) {
+ return Optional.empty();
+ }
+ final ShenyuConfig.TracingConfig.OtlpConfig otlp = configured.get();
+ try {
+ final String endpoint = endpoint(otlp.getEndpoint());
+ final OtlpHttpSpanExporterBuilder builder =
OtlpHttpSpanExporter.builder().setEndpoint(endpoint);
+ if (Objects.nonNull(otlp.getTimeout())) {
+ builder.setTimeout(otlp.getTimeout());
+ }
+ if (Objects.nonNull(otlp.getHeaders())) {
+ final Properties headers = otlp.getHeaders();
+ headers.stringPropertyNames().forEach(name ->
builder.addHeader(name, headers.getProperty(name)));
+ }
+ return Optional.of(builder.build());
+ } catch (RuntimeException | LinkageError ex) {
+ // the message of the failure may quote the endpoint, so only its
type is logged at warn
+ // level while the details stay at debug level
+ LOG.warn("shenyu tracing could not create the OTLP exporter of {},
the spans are not exported: {}",
+ redactedEndpoint(otlp.getEndpoint()),
ex.getClass().getSimpleName());
+ LOG.debug("shenyu tracing could not create the OTLP exporter", ex);
Review Comment:
Logging the raw exception at DEBUG bypasses endpoint redaction. For example,
a credential-bearing endpoint with an unescaped space makes `URI.create()`
throw an exception containing the entire URL, including user information and
query tokens. When DEBUG logging is enabled, those credentials reach the logs.
Remove the throwable logging and retain only the redacted endpoint and
exception type; credentials must remain protected at every log level.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]