Log correlation IDs when Micrometer tracing is being used
Add support for logging correlation IDs with Logback or Log4J2 whenever Micrometer tracing is being used. The `LoggingSystemProperties` class now accepts a defualt value resolver which will be used whenever a value isn't in the environment. The `AbstractLoggingSystem` provides a resolver that supports the `logging.pattern.correlation` property and will return a value whenever `LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY` is set. Using `LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY` allows us to provide a consistent width for the correlation ID, even when it's missing from the MDC. The exact correlation pattern returned will depend on the `LoggingSytem` implementation. Currently Logback and Log4J2 are supported and both make use of a custom converter which delegates to a new `CorrelationIdFormatter` class. Closes gh-33280pull/36032/head
parent
b6120d504a
commit
c1b295fd71
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.boot.logging.LoggingSystem;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link EnvironmentPostProcessor} to add a {@link PropertySource} to support log
|
||||
* correlation IDs when Micrometer is present. Adds support for the
|
||||
* {@value LoggingSystem#EXPECT_CORRELATION_ID_PROPERTY} property by delegating to
|
||||
* {@code management.tracing.enabled}.
|
||||
*
|
||||
* @author Jonatan Ivanov
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LogCorrelationEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
if (ClassUtils.isPresent("io.micrometer.tracing.Tracer", application.getClassLoader())) {
|
||||
environment.getPropertySources().addLast(new LogCorrelationPropertySource(this, environment));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log correlation {@link PropertySource}.
|
||||
*/
|
||||
private static class LogCorrelationPropertySource extends PropertySource<Object> {
|
||||
|
||||
private static final String NAME = "logCorrelation";
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
LogCorrelationPropertySource(Object source, Environment environment) {
|
||||
super(NAME, source);
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
if (name.equals(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY)) {
|
||||
return this.environment.getProperty("management.tracing.enabled", Boolean.class, Boolean.TRUE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
# Failure Analyzers
|
||||
org.springframework.boot.diagnostics.FailureAnalyzer=\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer
|
||||
|
||||
# Environment Post Processors
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
org.springframework.boot.actuate.autoconfigure.tracing.LogCorrelationEnvironmentPostProcessor
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.tracing;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.logging.LoggingSystem;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link LogCorrelationEnvironmentPostProcessor}.
|
||||
*
|
||||
* @author Jonatan Ivanov
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LogCorrelationEnvironmentPostProcessorTests {
|
||||
|
||||
private final ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
|
||||
private final SpringApplication application = new SpringApplication();
|
||||
|
||||
private final LogCorrelationEnvironmentPostProcessor postProcessor = new LogCorrelationEnvironmentPostProcessor();
|
||||
|
||||
@Test
|
||||
void getExpectCorrelationIdPropertyWhenMicrometerPresentReturnsTrue() {
|
||||
this.postProcessor.postProcessEnvironment(this.environment, this.application);
|
||||
assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@ClassPathExclusions("micrometer-tracing-*.jar")
|
||||
void getExpectCorrelationIdPropertyWhenMicrometerMissingReturnsFalse() {
|
||||
this.postProcessor.postProcessEnvironment(this.environment, this.application);
|
||||
assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getExpectCorrelationIdPropertyWhenTracingDisabledReturnsFalse() {
|
||||
TestPropertyValues.of("management.tracing.enabled=false").applyTo(this.environment);
|
||||
this.postProcessor.postProcessEnvironment(this.environment, this.application);
|
||||
assertThat(this.environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to format a correlation identifier for logging based on
|
||||
* <a href=
|
||||
* "https://www.w3.org/TR/trace-context/#examples-of-http-traceparent-headers">w3c</a>
|
||||
* recommendations.
|
||||
* <p>
|
||||
* The formatter can be configured with a comma-separated list of names and the expected
|
||||
* length of their resolved value. Each item should be specified in the form
|
||||
* {@code "<name>(length)"}. For example, {@code "traceId(32),spanId(16)"} specifies the
|
||||
* names {@code "traceId"} and {@code "spanId"} with expected lengths of {@code 32} and
|
||||
* {@code 16} respectively.
|
||||
* <p>
|
||||
* Correlation IDs are formatted as dash separated strings surrounded in square brackets.
|
||||
* Formatted output is always of a fixed width and with trailing whitespace. Dashes are
|
||||
* omitted of none of the named items can be resolved.
|
||||
* <p>
|
||||
* The following example would return a formatted result of
|
||||
* {@code "[01234567890123456789012345678901-0123456789012345] "}: <pre class="code">
|
||||
* CorrelationIdFormatter formatter = CorrelationIdFormatter.of("traceId(32),spanId(16)");
|
||||
* Map<String, String> mdc = Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345");
|
||||
* return formatter.format(mdc::get);
|
||||
* </pre>
|
||||
* <p>
|
||||
* If {@link #of(String)} is called with an empty spec the {@link #DEFAULT} formatter will
|
||||
* be used.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 3.2.0
|
||||
* @see #of(String)
|
||||
* @see #of(Collection)
|
||||
*/
|
||||
public final class CorrelationIdFormatter {
|
||||
|
||||
/**
|
||||
* Default {@link CorrelationIdFormatter}.
|
||||
*/
|
||||
public static final CorrelationIdFormatter DEFAULT = CorrelationIdFormatter.of("traceId(32),spanId(16)");
|
||||
|
||||
private final List<Part> parts;
|
||||
|
||||
private final String blank;
|
||||
|
||||
private CorrelationIdFormatter(List<Part> parts) {
|
||||
this.parts = parts;
|
||||
this.blank = String.format("[%s] ", parts.stream().map(Part::blank).collect(Collectors.joining(" ")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a correlation from the values in the given resolver.
|
||||
* @param resolver the resolver used to resolve named values
|
||||
* @return a formatted correlation id
|
||||
*/
|
||||
public String format(Function<String, String> resolver) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
formatTo(resolver, result);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a correlation from the values in the given resolver and append it to the
|
||||
* given {@link Appendable}.
|
||||
* @param resolver the resolver used to resolve named values
|
||||
* @param appendable the appendable for the formatted correlation id
|
||||
*/
|
||||
public void formatTo(Function<String, String> resolver, Appendable appendable) {
|
||||
Predicate<Part> canResolve = (part) -> StringUtils.hasLength(resolver.apply(part.name()));
|
||||
try {
|
||||
if (this.parts.stream().anyMatch(canResolve)) {
|
||||
appendable.append("[");
|
||||
for (Iterator<Part> iterator = this.parts.iterator(); iterator.hasNext();) {
|
||||
appendable.append(iterator.next().resolve(resolver));
|
||||
appendable.append((!iterator.hasNext()) ? "" : "-");
|
||||
}
|
||||
appendable.append("] ");
|
||||
}
|
||||
else {
|
||||
appendable.append(this.blank);
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.parts.stream().map(Part::toString).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CorrelationIdFormatter} instance from the given specification.
|
||||
* @param spec a comma separated specification
|
||||
* @return a new {@link CorrelationIdFormatter} instance
|
||||
*/
|
||||
public static CorrelationIdFormatter of(String spec) {
|
||||
try {
|
||||
return (!StringUtils.hasText(spec)) ? DEFAULT : of(List.of(spec.split(",")));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to parse correlation formatter spec '%s'".formatted(spec), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CorrelationIdFormatter} instance from the given specification.
|
||||
* @param spec a pre-separated specification
|
||||
* @return a new {@link CorrelationIdFormatter} instance
|
||||
*/
|
||||
public static CorrelationIdFormatter of(String[] spec) {
|
||||
return of((spec != null) ? Arrays.asList(spec) : Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link CorrelationIdFormatter} instance from the given specification.
|
||||
* @param spec a pre-separated specification
|
||||
* @return a new {@link CorrelationIdFormatter} instance
|
||||
*/
|
||||
public static CorrelationIdFormatter of(Collection<String> spec) {
|
||||
if (CollectionUtils.isEmpty(spec)) {
|
||||
return DEFAULT;
|
||||
}
|
||||
List<Part> parts = spec.stream().map(Part::of).toList();
|
||||
return new CorrelationIdFormatter(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* A part of the correlation id.
|
||||
*
|
||||
* @param name the name of the correlation part
|
||||
* @param length the expected length of the correlation part
|
||||
*/
|
||||
static final record Part(String name, int length) {
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)?$");
|
||||
|
||||
String resolve(Function<String, String> resolver) {
|
||||
String resolved = resolver.apply(name());
|
||||
if (resolved == null) {
|
||||
return blank();
|
||||
}
|
||||
int padding = length() - resolved.length();
|
||||
return resolved + " ".repeat((padding > 0) ? padding : 0);
|
||||
}
|
||||
|
||||
String blank() {
|
||||
return " ".repeat(this.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "%s(%s)".formatted(name(), length());
|
||||
}
|
||||
|
||||
static Part of(String part) {
|
||||
Matcher matcher = pattern.matcher(part.trim());
|
||||
Assert.state(matcher.matches(), () -> "Invalid specification part '%s'".formatted(part));
|
||||
String name = matcher.group(1);
|
||||
int length = Integer.parseInt(matcher.group(2));
|
||||
return new Part(name, length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.log4j2;
|
||||
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||
import org.apache.logging.log4j.core.pattern.ConverterKeys;
|
||||
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
|
||||
import org.apache.logging.log4j.core.pattern.MdcPatternConverter;
|
||||
import org.apache.logging.log4j.core.pattern.PatternConverter;
|
||||
import org.apache.logging.log4j.util.PerformanceSensitive;
|
||||
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
||||
|
||||
import org.springframework.boot.logging.CorrelationIdFormatter;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Log4j2 {@link LogEventPatternConverter} to convert a {@link CorrelationIdFormatter}
|
||||
* pattern into formatted output using data from the {@link LogEvent#getContextData()
|
||||
* MDC}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 3.2.0
|
||||
* @see MdcPatternConverter
|
||||
*/
|
||||
@Plugin(name = "CorrelationIdConverter", category = PatternConverter.CATEGORY)
|
||||
@ConverterKeys("correlationId")
|
||||
@PerformanceSensitive("allocation")
|
||||
public final class CorrelationIdConverter extends LogEventPatternConverter {
|
||||
|
||||
private final CorrelationIdFormatter formatter;
|
||||
|
||||
private CorrelationIdConverter(CorrelationIdFormatter formatter) {
|
||||
super("correlationId{%s}".formatted(formatter), "mdc");
|
||||
this.formatter = formatter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void format(LogEvent event, StringBuilder toAppendTo) {
|
||||
ReadOnlyStringMap contextData = event.getContextData();
|
||||
this.formatter.formatTo(contextData::getValue, toAppendTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@link CorrelationIdConverter}.
|
||||
* @param options options, may be null or first element contains name of property to
|
||||
* format.
|
||||
* @return instance of PropertiesPatternConverter.
|
||||
*/
|
||||
public static CorrelationIdConverter newInstance(String[] options) {
|
||||
String pattern = (!ObjectUtils.isEmpty(options)) ? options[0] : null;
|
||||
return new CorrelationIdConverter(CorrelationIdFormatter.of(pattern));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.logback;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ch.qos.logback.classic.pattern.MDCConverter;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.pattern.DynamicConverter;
|
||||
|
||||
import org.springframework.boot.logging.CorrelationIdFormatter;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Logback {@link DynamicConverter} to convert a {@link CorrelationIdFormatter} pattern
|
||||
* into formatted output using data from the {@link ILoggingEvent#getMDCPropertyMap() MDC}
|
||||
* and {@link Environment}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 3.2.0
|
||||
* @see MDCConverter
|
||||
*/
|
||||
public class CorrelationIdConverter extends DynamicConverter<ILoggingEvent> {
|
||||
|
||||
private CorrelationIdFormatter formatter;
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.formatter = CorrelationIdFormatter.of(getOptionList());
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.formatter = null;
|
||||
super.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convert(ILoggingEvent event) {
|
||||
if (this.formatter == null) {
|
||||
return "";
|
||||
}
|
||||
Map<String, String> mdc = event.getMDCPropertyMap();
|
||||
return this.formatter.format(mdc::get);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link CorrelationIdFormatter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CorrelationIdFormatterTests {
|
||||
|
||||
@Test
|
||||
void formatWithDefaultSpecWhenHasBothParts() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("traceId", "01234567890123456789012345678901");
|
||||
context.put("spanId", "0123456789012345");
|
||||
String formatted = CorrelationIdFormatter.DEFAULT.format(context::get);
|
||||
assertThat(formatted).isEqualTo("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithDefaultSpecWhenHasNoParts() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
String formatted = CorrelationIdFormatter.DEFAULT.format(context::get);
|
||||
assertThat(formatted).isEqualTo("[ ] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithDefaultSpecWhenHasOnlyFirstPart() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("traceId", "01234567890123456789012345678901");
|
||||
String formatted = CorrelationIdFormatter.DEFAULT.format(context::get);
|
||||
assertThat(formatted).isEqualTo("[01234567890123456789012345678901- ] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithDefaultSpecWhenHasOnlySecondPart() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("spanId", "0123456789012345");
|
||||
String formatted = CorrelationIdFormatter.DEFAULT.format(context::get);
|
||||
assertThat(formatted).isEqualTo("[ -0123456789012345] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWhenPartsAreShort() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("traceId", "0123456789012345678901234567");
|
||||
context.put("spanId", "012345678901");
|
||||
String formatted = CorrelationIdFormatter.DEFAULT.format(context::get);
|
||||
assertThat(formatted).isEqualTo("[0123456789012345678901234567 -012345678901 ] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWhenPartsAreLong() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("traceId", "01234567890123456789012345678901FFFF");
|
||||
context.put("spanId", "0123456789012345FFFF");
|
||||
String formatted = CorrelationIdFormatter.DEFAULT.format(context::get);
|
||||
assertThat(formatted).isEqualTo("[01234567890123456789012345678901FFFF-0123456789012345FFFF] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatWithCustomSpec() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("a", "01234567890123456789012345678901");
|
||||
context.put("b", "0123456789012345");
|
||||
String formatted = CorrelationIdFormatter.of("a(32),b(16)").format(context::get);
|
||||
assertThat(formatted).isEqualTo("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatToWithDefaultSpec() {
|
||||
Map<String, String> context = new HashMap<>();
|
||||
context.put("traceId", "01234567890123456789012345678901");
|
||||
context.put("spanId", "0123456789012345");
|
||||
StringBuilder formatted = new StringBuilder();
|
||||
CorrelationIdFormatter.of("").formatTo(context::get, formatted);
|
||||
assertThat(formatted).hasToString("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWhenSpecIsMalformed() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> CorrelationIdFormatter.of("good(12),bad"))
|
||||
.withMessage("Unable to parse correlation formatter spec 'good(12),bad'")
|
||||
.havingCause()
|
||||
.withMessage("Invalid specification part 'bad'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ofWhenSpecIsEmpty() {
|
||||
assertThat(CorrelationIdFormatter.of("")).isSameAs(CorrelationIdFormatter.DEFAULT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringReturnsSpec() {
|
||||
assertThat(CorrelationIdFormatter.DEFAULT).hasToString("traceId(32),spanId(16)");
|
||||
assertThat(CorrelationIdFormatter.of("a(32),b(16)")).hasToString("a(32),b(16)");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.log4j2;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.core.AbstractLogEvent;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
|
||||
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CorrelationIdConverter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CorrelationIdConverterTests {
|
||||
|
||||
private CorrelationIdConverter converter = CorrelationIdConverter.newInstance(null);
|
||||
|
||||
private final LogEvent event = new TestLogEvent();
|
||||
|
||||
@Test
|
||||
void defaultPattern() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
this.converter.format(this.event, result);
|
||||
assertThat(result).hasToString("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customPattern() {
|
||||
this.converter = CorrelationIdConverter.newInstance(new String[] { "traceId(0),spanId(0)" });
|
||||
StringBuilder result = new StringBuilder();
|
||||
this.converter.format(this.event, result);
|
||||
assertThat(result).hasToString("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
static class TestLogEvent extends AbstractLogEvent {
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringMap getContextData() {
|
||||
return new JdkMapAdapterStringMap(
|
||||
Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.logback;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.LoggingEvent;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CorrelationIdConverter}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class CorrelationIdConverterTests {
|
||||
|
||||
private final CorrelationIdConverter converter;
|
||||
|
||||
private final LoggingEvent event = new LoggingEvent();
|
||||
|
||||
CorrelationIdConverterTests() {
|
||||
this.converter = new CorrelationIdConverter();
|
||||
this.converter.setContext(new LoggerContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultPattern() {
|
||||
addMdcProperties(this.event);
|
||||
this.converter.start();
|
||||
String converted = this.converter.convert(this.event);
|
||||
this.converter.stop();
|
||||
assertThat(converted).isEqualTo("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customPattern() {
|
||||
this.converter.setOptionList(List.of("traceId(0)", "spanId(0)"));
|
||||
addMdcProperties(this.event);
|
||||
this.converter.start();
|
||||
String converted = this.converter.convert(this.event);
|
||||
this.converter.stop();
|
||||
assertThat(converted).isEqualTo("[01234567890123456789012345678901-0123456789012345] ");
|
||||
}
|
||||
|
||||
private void addMdcProperties(LoggingEvent event) {
|
||||
event.setMDCPropertyMap(Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345"));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue