package com.ankurm.otel; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.context.ConfigurableApplicationContext; import static org.assertj.core.api.Assertions.assertThat; /** * Two smaller 4.2 changes that break dashboards and log queries rather than builds: the MDC keys * that carry the trace and span ids, and the active-observation LongTaskTimer that is no longer * created by default. */ @ExtendWith(OutputCaptureExtension.class) class MdcAndLongTaskTimerTest { @Test void mdcKeys(CapturedOutput output) { Transcript t = new Transcript("04-mdc-keys"); // The pattern reads both the default key names and the custom ones, so one log line shows // which key the tracing bridge really populated. ConfigurableApplicationContext ctx = Apps.start("management.otlp.metrics.export.enabled=false", "management.tracing.mdc.trace-id-key=trace_id", "management.tracing.mdc.span-id-key=span_id", "logging.pattern.correlation=default[%X{traceId:-}] custom[%X{trace_id:-}] "); Apps.call(ctx, "/hello/ankur"); ctx.close(); String line = output.getAll().lines().filter(l -> l.contains("handling hello for ankur")).findFirst().orElseThrow(); String correlation = line.substring(line.indexOf("default["), line.indexOf("] : ") > 0 ? line.indexOf("] :") : line.length()); t.line("management.tracing.mdc.trace-id-key = trace_id"); t.line("management.tracing.mdc.span-id-key = span_id"); t.line("log pattern correlation part reads both MDC keys"); t.line(""); t.line("log line: " + line.replaceAll("^\\S+\\s+", "").replaceAll("(?<=\\[)[0-9a-f]{32}(?=\\])", "<32-hex-trace-id>")); boolean defaultKeyFilled = line.matches(".*default\\[[0-9a-f]{32}\\].*"); boolean customKeyFilled = line.matches(".*custom\\[[0-9a-f]{32}\\].*"); t.line("default key traceId populated: " + defaultKeyFilled); t.line("custom key trace_id populated: " + customKeyFilled); if (Transcript.is42()) { assertThat(customKeyFilled).isTrue(); assertThat(defaultKeyFilled).isFalse(); } else { assertThat(customKeyFilled).isFalse(); assertThat(defaultKeyFilled).isTrue(); } t.write(); } @Test void activeLongTaskTimer() throws Exception { Transcript t = new Transcript("05-active-long-task-timer"); for (String setting : List.of("(default)", "management.metrics.observations.include-active-long-task-timer=true")) { ConfigurableApplicationContext ctx = setting.startsWith("(") ? Apps.start("management.otlp.metrics.export.enabled=false") : Apps.start("management.otlp.metrics.export.enabled=false", setting); Apps.call(ctx, "/hello/ankur"); MeterRegistry registry = ctx.getBean(MeterRegistry.class); Apps.awaitHttpMeter(registry); TreeSet names = new TreeSet<>(); for (Meter m : registry.getMeters()) { if (m.getId().getName().startsWith("http.server")) { names.add(m.getId().getName() + " (" + m.getId().getType() + ")"); } } t.line(""); t.line("== " + setting); names.forEach(n -> t.line(" " + n)); boolean active = names.stream().anyMatch(n -> n.startsWith("http.server.requests.active")); t.line(" http.server.requests.active present: " + active); if (Transcript.is42()) { assertThat(active).isEqualTo(!setting.startsWith("(")); } else if (setting.startsWith("(")) { assertThat(active).isTrue(); } ctx.close(); } t.write(); } }