All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.boot.logging.CorrelationIdFormatter Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/*
 * 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.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
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
 * W3C
 * recommendations.
 * 

* 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 "(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. *

* Correlation IDs are formatted as dash separated strings surrounded in square brackets. * Formatted output is always of a fixed width and with trailing space. Dashes are omitted * if none of the named items can be resolved. *

* The following example would return a formatted result of * {@code "[01234567890123456789012345678901-0123456789012345] "}:

 * CorrelationIdFormatter formatter = CorrelationIdFormatter.of("traceId(32),spanId(16)");
 * Map<String, String> mdc = Map.of("traceId", "01234567890123456789012345678901", "spanId", "0123456789012345");
 * return formatter.format(mdc::get);
 * 
*

* 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 parts; private final String blank; private CorrelationIdFormatter(List 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(UnaryOperator 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(UnaryOperator resolver, Appendable appendable) { Predicate canResolve = (part) -> StringUtils.hasLength(resolver.apply(part.name())); try { if (this.parts.stream().anyMatch(canResolve)) { appendable.append('['); for (Iterator iterator = this.parts.iterator(); iterator.hasNext();) { appendable.append(iterator.next().resolve(resolver)); if (iterator.hasNext()) { appendable.append('-'); } } 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) ? List.of(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 spec) { if (CollectionUtils.isEmpty(spec)) { return DEFAULT; } List 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 */ record Part(String name, int length) { private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)$"); String resolve(UnaryOperator resolver) { String resolved = resolver.apply(name()); if (resolved == null) { return blank(); } int padding = length() - resolved.length(); return (padding <= 0) ? resolved : resolved + " ".repeat(padding); } 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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy