Compare commits

..

No commits in common. 'main' and '3.0.x' have entirely different histories.
main ... 3.0.x

@ -2,6 +2,5 @@
# Reformat code following spring-javaformat upgrade
df5898a1464112f185d295d585740de696934a12
c4de86c244acdcff69ed0aecacd254399be79ce2
b07269a018a4a9d4c029aba7dd8a15fa66df681c

@ -2,16 +2,6 @@
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="LombokGetterMayBeUsed" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="NullableProblems" enabled="false" level="WARNING" enabled_by_default="false">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE" value="true" />
<option name="REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_GETTER" value="true" />
<option name="REPORT_NOT_ANNOTATED_SETTER_PARAMETER" value="true" />
<option name="REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS" value="true" />
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
</inspection_tool>
<inspection_tool class="UnqualifiedFieldAccess" enabled="true" level="ERROR" enabled_by_default="true" editorAttributes="ERRORS_ATTRIBUTES" />
</profile>
</component>

@ -5,7 +5,6 @@
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,

@ -1,4 +1,4 @@
= Spring Boot image:https://ci.spring.io/api/v1/teams/spring-boot/pipelines/spring-boot-3.2.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-3.2.x?groups=Build"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"]
= Spring Boot image:https://ci.spring.io/api/v1/teams/spring-boot/pipelines/spring-boot-3.0.x/jobs/build/badge["Build Status", link="https://ci.spring.io/teams/spring-boot/pipelines/spring-boot-3.0.x?groups=Build"] image:https://badges.gitter.im/Join Chat.svg["Chat",link="https://gitter.im/spring-projects/spring-boot?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Gradle Enterprise", link="https://ge.spring.io/scans?&search.rootProjectNames=Spring%20Boot%20Build&search.rootProjectNames=spring-boot-build"]
:docs: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference
:github: https://github.com/spring-projects/spring-boot
@ -10,7 +10,6 @@ We also provide a command-line tool that runs Spring scripts.
Our primary goals are:
* Provide a radically faster and widely accessible getting started experience for all Spring development.
* Be opinionated, but get out of the way quickly as requirements start to diverge from the defaults.
* Provide a range of non-functional features common to large classes of projects (for example, embedded servers, security, metrics, health checks, externalized configuration).

@ -6,8 +6,6 @@ plugins {
description = "Spring Boot Build"
defaultTasks 'build'
nohttp {

@ -18,11 +18,10 @@ new File(projectDir.parentFile, "gradle.properties").withInputStream {
def properties = new Properties()
properties.load(it)
["assertj", "commonsCodec", "hamcrest", "jackson", "junitJupiter",
"kotlin", "maven"].each {
"kotlin", "maven", "springFramework"].each {
versions[it] = properties[it + "Version"]
}
}
versions["springFramework"] = "6.0.12"
ext.set("versions", versions)
if (versions.springFramework.contains("-")) {
repositories {

@ -134,7 +134,7 @@ class AsciidoctorConventions {
private String determineGitHubTag(Project project) {
String version = "v" + project.getVersion();
return (version.endsWith("-SNAPSHOT")) ? "main" : version;
return (version.endsWith("-SNAPSHOT")) ? "3.0.x" : version;
}
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {

@ -48,7 +48,6 @@ public class ConventionsPlugin implements Plugin<Project> {
new AsciidoctorConventions().apply(project);
new KotlinConventions().apply(project);
new WarConventions().apply(project);
new EclipseConventions().apply(project);
}
}

@ -1,69 +0,0 @@
/*
* Copyright 2023-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.build;
import org.gradle.api.Project;
import org.gradle.plugins.ide.api.XmlFileContentMerger;
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
import org.gradle.plugins.ide.eclipse.model.Classpath;
import org.gradle.plugins.ide.eclipse.model.ClasspathEntry;
import org.gradle.plugins.ide.eclipse.model.EclipseClasspath;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
import org.gradle.plugins.ide.eclipse.model.Library;
/**
* Conventions that are applied in the presence of the {@link EclipsePlugin} to work
* around buildship issue {@code #1238}.
*
* @author Phillip Webb
*/
class EclipseConventions {
void apply(Project project) {
project.getPlugins().withType(EclipsePlugin.class, (eclipse) -> {
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
eclipseModel.classpath(this::configureClasspath);
});
}
private void configureClasspath(EclipseClasspath classpath) {
classpath.file(this::configureClasspathFile);
}
private void configureClasspathFile(XmlFileContentMerger merger) {
merger.whenMerged((content) -> {
if (content instanceof Classpath classpath) {
classpath.getEntries().removeIf(this::isKotlinPluginContributedBuildDirectory);
}
});
}
private boolean isKotlinPluginContributedBuildDirectory(ClasspathEntry entry) {
return (entry instanceof Library library) && isKotlinPluginContributedBuildDirectory(library.getPath())
&& isTest(library);
}
private boolean isKotlinPluginContributedBuildDirectory(String path) {
return path.contains("/main") && (path.contains("/build/classes/") || path.contains("/build/resources/"));
}
private boolean isTest(Library library) {
Object value = library.getEntryAttributes().get("test");
return (value instanceof String string && Boolean.parseBoolean(string));
}
}

@ -42,6 +42,7 @@ import org.gradle.api.tasks.SourceSet;
import org.springframework.boot.build.DeployedPlugin;
import org.springframework.boot.build.architecture.ArchitectureCheck;
import org.springframework.boot.build.architecture.ArchitecturePlugin;
import org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin;
/**
* {@link Plugin} for projects that define auto-configuration. When applied, the plugin
@ -49,6 +50,7 @@ import org.springframework.boot.build.architecture.ArchitecturePlugin;
* applied it:
*
* <ul>
* <li>Applies the {@link ConfigurationPropertiesPlugin}.
* <li>Adds a dependency on the auto-configuration annotation processor.
* <li>Defines a task that produces metadata describing the auto-configuration. The
* metadata is made available as an artifact in the {@code autoConfigurationMetadata}
@ -77,6 +79,7 @@ public class AutoConfigurationPlugin implements Plugin<Project> {
public void apply(Project project) {
project.getPlugins().apply(DeployedPlugin.class);
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
project.getPlugins().apply(ConfigurationPropertiesPlugin.class);
Configuration annotationProcessors = project.getConfigurations()
.getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME);
annotationProcessors.getDependencies()

@ -58,18 +58,7 @@ final class MavenMetadataVersionResolver implements VersionResolver {
MavenMetadataVersionResolver(RestTemplate restTemplate, Collection<URI> repositoryUrls) {
this.rest = restTemplate;
this.repositoryUrls = normalize(repositoryUrls);
}
private Collection<URI> normalize(Collection<URI> uris) {
return uris.stream().map(this::normalize).toList();
}
private URI normalize(URI uri) {
if ("/".equals(uri.getPath())) {
return uri;
}
return URI.create(uri.toString() + "/");
this.repositoryUrls = repositoryUrls;
}
@Override

@ -19,11 +19,13 @@ package org.springframework.boot.build.bom.bomr;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -94,14 +96,19 @@ class StandardLibraryUpdateResolver implements LibraryUpdateResolver {
getLaterVersionsForModule(group.getId(), plugin, library));
}
}
return moduleVersions.values()
List<DependencyVersion> allVersions = moduleVersions.values()
.stream()
.flatMap(SortedSet::stream)
.distinct()
.filter((dependencyVersion) -> this.predicate.test(library, dependencyVersion))
.map((version) -> (VersionOption) new VersionOption.ResolvedVersionOption(version,
getMissingModules(moduleVersions, version)))
.toList();
if (allVersions.isEmpty()) {
return Collections.emptyList();
}
return allVersions.stream()
.map((version) -> new VersionOption.ResolvedVersionOption(version,
getMissingModules(moduleVersions, version)))
.collect(Collectors.toList());
}
private List<String> getMissingModules(Map<String, SortedSet<DependencyVersion>> moduleVersions,

@ -16,7 +16,6 @@
package org.springframework.boot.build.bom.bomr.version;
import java.util.Objects;
import java.util.Optional;
import org.apache.maven.artifact.versioning.ArtifactVersion;
@ -122,21 +121,6 @@ class ArtifactVersionDependencyVersion extends AbstractDependencyVersion {
return sameMajorMinorIncremental(((ArtifactVersionDependencyVersion) candidate).artifactVersion);
}
@Override
public int compareTo(DependencyVersion other) {
if (other instanceof ArtifactVersionDependencyVersion otherArtifactDependencyVersion) {
ArtifactVersion otherArtifactVersion = otherArtifactDependencyVersion.artifactVersion;
if ((!Objects.equals(this.artifactVersion.getQualifier(), otherArtifactVersion.getQualifier()))
&& "snapshot".equalsIgnoreCase(otherArtifactVersion.getQualifier())
&& otherArtifactVersion.getMajorVersion() == this.artifactVersion.getMajorVersion()
&& otherArtifactVersion.getMinorVersion() == this.artifactVersion.getMinorVersion()
&& otherArtifactVersion.getIncrementalVersion() == this.artifactVersion.getIncrementalVersion()) {
return 1;
}
}
return super.compareTo(other);
}
@Override
public String toString() {
return this.artifactVersion.toString();

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -78,7 +78,6 @@ public class DocumentConfigurationProperties extends DefaultTask {
snippets.add("application-properties.security", "Security Properties", this::securityPrefixes);
snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes);
snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes);
snippets.add("application-properties.docker-compose", "Docker Compose Properties", this::dockerComposePrefixes);
snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes);
snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes);
snippets.writeTo(this.outputDir.toPath());
@ -104,9 +103,7 @@ public class DocumentConfigurationProperties extends DefaultTask {
config.accept("spring.profiles");
config.accept("spring.quartz");
config.accept("spring.reactor");
config.accept("spring.ssl");
config.accept("spring.task");
config.accept("spring.threads");
config.accept("spring.mandatory-file-encoding");
config.accept("info");
config.accept("spring.output.ansi.enabled");
@ -171,7 +168,6 @@ public class DocumentConfigurationProperties extends DefaultTask {
prefix.accept("spring.integration");
prefix.accept("spring.jms");
prefix.accept("spring.kafka");
prefix.accept("spring.pulsar");
prefix.accept("spring.rabbitmq");
prefix.accept("spring.hazelcast");
prefix.accept("spring.webservices");
@ -215,10 +211,6 @@ public class DocumentConfigurationProperties extends DefaultTask {
prefix.accept("management");
}
private void dockerComposePrefixes(Config prefix) {
prefix.accept("spring.docker.compose");
}
private void devtoolsPrefixes(Config prefix) {
prefix.accept("spring.devtools");
}

@ -105,8 +105,8 @@ public class ApplicationRunner extends DefaultTask {
}
public void normalizeTomcatPort() {
this.normalizations.put("(Tomcat started on port )[\\d]+( \\(http\\))", "$18080$2");
this.normalizations.put("(Tomcat initialized with port )[\\d]+( \\(http\\))", "$18080$2");
this.normalizations.put("(Tomcat started on port\\(s\\): )[\\d]+( \\(http\\))", "$18080$2");
this.normalizations.put("(Tomcat initialized with port\\(s\\): )[\\d]+( \\(http\\))", "$18080$2");
}
public void normalizeLiveReloadPort() {

@ -86,8 +86,8 @@ public class MavenExec extends JavaExec {
return existing;
}
return project.getConfigurations().create("maven", (maven) -> {
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-embedder:3.6.3"));
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-compat:3.6.3"));
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-embedder:3.6.2"));
maven.getDependencies().add(project.getDependencies().create("org.apache.maven:maven-compat:3.6.2"));
maven.getDependencies().add(project.getDependencies().create("org.slf4j:slf4j-simple:1.7.5"));
maven.getDependencies()
.add(project.getDependencies()

@ -11,7 +11,7 @@ The pipeline can be deployed using the following command:
[source]
----
$ fly -t spring-boot set-pipeline -p spring-boot-3.2.x -c ci/pipeline.yml -l ci/parameters.yml
$ fly -t spring-boot set-pipeline -p spring-boot-3.0.x -c ci/pipeline.yml -l ci/parameters.yml
----
NOTE: This assumes that you have credhub integration configured with the appropriate

@ -3,12 +3,8 @@ FROM ubuntu:jammy-20230916
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
ADD get-docker-url.sh /get-docker-url.sh
ADD get-docker-compose-url.sh /get-docker-compose-url.sh
RUN ./setup.sh java17 java21
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH
ADD docker-lib.sh /docker-lib.sh

@ -3,12 +3,8 @@ FROM ubuntu:jammy-20230916
ADD setup.sh /setup.sh
ADD get-jdk-url.sh /get-jdk-url.sh
ADD get-docker-url.sh /get-docker-url.sh
ADD get-docker-compose-url.sh /get-docker-compose-url.sh
RUN ./setup.sh java17
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV JAVA_HOME /opt/openjdk
ENV PATH $JAVA_HOME/bin:$PATH
ADD docker-lib.sh /docker-lib.sh

@ -1,5 +0,0 @@
#!/bin/bash
set -e
version="2.17.0"
echo "https://github.com/docker/compose/releases/download/v$version/docker-compose-linux-x86_64"

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2021 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.
@ -21,6 +21,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.stream.Collectors;
@ -104,7 +105,7 @@ class SonatypeServiceTests {
.filter((artifact) -> !artifact.startsWith("build-info.json"))
.map((artifact) -> requestTo(
"/service/local/staging/deployByRepositoryId/example-6789/" + artifact.toString()))
.collect(Collectors.toSet());
.collect(Collectors.toCollection(HashSet::new));
AnyOfRequestMatcher uploadRequestsMatcher = anyOf(uploads);
assertThat(uploadRequestsMatcher.candidates).hasSize(150);
this.server.expect(ExpectedCount.times(150), uploadRequestsMatcher).andExpect(method(HttpMethod.PUT))
@ -132,7 +133,7 @@ class SonatypeServiceTests {
.andRespond(withSuccess());
this.service.publish(getReleaseInfo(), artifactsRoot);
this.server.verify();
assertThat(uploadRequestsMatcher.candidates).isEmpty();
assertThat(uploadRequestsMatcher.candidates).hasSize(0);
}
}
@ -156,7 +157,7 @@ class SonatypeServiceTests {
.filter((artifact) -> !"build-info.json".equals(artifact.toString()))
.map((artifact) -> requestTo(
"/service/local/staging/deployByRepositoryId/example-6789/" + artifact.toString()))
.collect(Collectors.toSet());
.collect(Collectors.toCollection(HashSet::new));
AnyOfRequestMatcher uploadRequestsMatcher = anyOf(uploads);
assertThat(uploadRequestsMatcher.candidates).hasSize(150);
this.server.expect(ExpectedCount.times(150), uploadRequestsMatcher).andExpect(method(HttpMethod.PUT))
@ -184,7 +185,7 @@ class SonatypeServiceTests {
.isThrownBy(() -> this.service.publish(getReleaseInfo(), artifactsRoot))
.withMessage("Close failed");
this.server.verify();
assertThat(uploadRequestsMatcher.candidates).isEmpty();
assertThat(uploadRequestsMatcher.candidates).hasSize(0);
}
}

@ -2,13 +2,12 @@
set -ex
###########################################################
# OS and UTILS
# UTILS
###########################################################
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --no-install-recommends -y locales tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq
locale-gen en_US.utf8
apt-get install --no-install-recommends -y tzdata ca-certificates net-tools libxml2-utils git curl libudev1 libxml2-utils iptables iproute2 jq
ln -fs /usr/share/zoneinfo/UTC /etc/localtime
dpkg-reconfigure --frontend noninteractive tzdata
rm -rf /var/lib/apt/lists/*
@ -38,7 +37,6 @@ if [[ $# -eq 2 ]]; then
test -f /opt/openjdk-toolchain/bin/javac
fi
###########################################################
# DOCKER
###########################################################
@ -53,12 +51,3 @@ curl -L https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSI
chmod +x entrykit && \
mv entrykit /bin/entrykit && \
entrykit --symlink
###########################################################
# DOCKER COMPOSE
###########################################################
mkdir -p /usr/local/lib/docker/cli-plugins
DOCKER_COMPOSE_URL=$( ./get-docker-compose-url.sh )
curl -L ${DOCKER_COMPOSE_URL} -o /usr/local/lib/docker/cli-plugins/docker-compose
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

@ -3,8 +3,8 @@ github-repo-name: "spring-projects/spring-boot"
homebrew-tap-repo: "https://github.com/spring-io/homebrew-tap.git"
docker-hub-organization: "springci"
artifactory-server: "https://repo.spring.io"
branch: "main"
milestone: "3.2.x"
branch: "3.0.x"
milestone: "3.0.x"
build-name: "spring-boot"
concourse-url: "https://ci.spring.io"
task-timeout: 2h00m

@ -179,8 +179,8 @@ resources:
type: registry-image
icon: docker
source:
repository: paketobuildpacks/builder-jammy-base
tag: latest
repository: paketobuildpacks/builder
tag: base
- name: artifactory-repo
type: artifactory-resource
icon: package-variant

@ -11,8 +11,8 @@
xmlns:setup.workingsets="http://www.eclipse.org/oomph/setup/workingsets/1.0"
xmlns:workingsets="http://www.eclipse.org/oomph/workingsets/1.0"
xsi:schemaLocation="http://www.eclipse.org/oomph/setup/jdt/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/JDT.ecore http://www.eclipse.org/buildship/oomph/1.0 https://raw.githubusercontent.com/eclipse/buildship/master/org.eclipse.buildship.oomph/model/GradleImport-1.0.ecore http://www.eclipse.org/oomph/predicates/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/Predicates.ecore http://www.eclipse.org/oomph/setup/workingsets/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/SetupWorkingSets.ecore http://www.eclipse.org/oomph/workingsets/1.0 http://git.eclipse.org/c/oomph/org.eclipse.oomph.git/plain/setups/models/WorkingSets.ecore"
name="spring.boot.3.2.x"
label="Spring Boot 3.2.x">
name="spring.boot.3.0.x"
label="Spring Boot 3.0.x">
<setupTask
xsi:type="setup:VariableTask"
type="FOLDER"
@ -136,7 +136,7 @@
name="spring-boot-tools">
<predicate
xsi:type="predicates:NamePredicate"
pattern="spring-boot-(tools|antlib|configuration-.*|loader|loader-classic|.*-tools|.*-layertools|.*-plugin|autoconfigure-processor|buildpack.*)"/>
pattern="spring-boot-(tools|antlib|configuration-.*|loader|.*-tools|.*-layertools|.*-plugin|autoconfigure-processor|buildpack.*)"/>
</workingSet>
<workingSet
name="spring-boot-starters">

@ -4,7 +4,7 @@ require 'net/http'
require 'yaml'
require 'logger'
$main_branch = "3.2.x"
$main_branch = "3.0.x"
$log = Logger.new(STDOUT)
$log.level = Logger::WARN

@ -1,18 +1,18 @@
version=3.2.0-SNAPSHOT
version=3.0.12-SNAPSHOT
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
assertjVersion=3.24.2
commonsCodecVersion=1.16.0
assertjVersion=3.23.1
commonsCodecVersion=1.15
hamcrestVersion=2.2
jacksonVersion=2.15.2
junitJupiterVersion=5.10.0
kotlinVersion=1.9.10
jacksonVersion=2.14.3
junitJupiterVersion=5.9.3
kotlinVersion=1.7.22
mavenVersion=3.9.4
nativeBuildToolsVersion=0.9.27
springFrameworkVersion=6.1.0-SNAPSHOT
springFrameworkVersion=6.0.13-SNAPSHOT
tomcatVersion=10.1.13
kotlin.stdlib.default.dependency=false

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -5,7 +5,6 @@ pluginManagement {
if (version.endsWith('-SNAPSHOT')) {
maven { url "https://repo.spring.io/snapshot" }
}
}
resolutionStrategy {
eachPlugin {
@ -54,13 +53,11 @@ include "spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-process
include "spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform"
include "spring-boot-project:spring-boot-tools:spring-boot-cli"
include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata"
include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata-changelog-generator"
include "spring-boot-project:spring-boot-tools:spring-boot-configuration-processor"
include "spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin"
include "spring-boot-project:spring-boot-tools:spring-boot-gradle-test-support"
include "spring-boot-project:spring-boot-tools:spring-boot-jarmode-layertools"
include "spring-boot-project:spring-boot-tools:spring-boot-loader"
include "spring-boot-project:spring-boot-tools:spring-boot-loader-classic"
include "spring-boot-project:spring-boot-tools:spring-boot-loader-tools"
include "spring-boot-project:spring-boot-tools:spring-boot-maven-plugin"
include "spring-boot-project:spring-boot-tools:spring-boot-properties-migrator"
@ -69,16 +66,13 @@ include "spring-boot-project:spring-boot"
include "spring-boot-project:spring-boot-autoconfigure"
include "spring-boot-project:spring-boot-actuator"
include "spring-boot-project:spring-boot-actuator-autoconfigure"
include "spring-boot-project:spring-boot-docker-compose"
include "spring-boot-project:spring-boot-devtools"
include "spring-boot-project:spring-boot-docs"
include "spring-boot-project:spring-boot-test"
include "spring-boot-project:spring-boot-testcontainers"
include "spring-boot-project:spring-boot-test-autoconfigure"
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-configuration-processor-tests"
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-launch-script-tests"
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-tests"
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-loader-classic-tests"
include "spring-boot-tests:spring-boot-integration-tests:spring-boot-server-tests"
include "spring-boot-system-tests:spring-boot-deployment-tests"
include "spring-boot-system-tests:spring-boot-image-tests"

@ -2,7 +2,6 @@ plugins {
id "java-library"
id "org.asciidoctor.jvm.convert"
id "org.springframework.boot.auto-configuration"
id "org.springframework.boot.configuration-properties"
id "org.springframework.boot.conventions"
id "org.springframework.boot.deployed"
id "org.springframework.boot.optional-dependencies"
@ -19,6 +18,7 @@ dependencies {
asciidoctorExtensions("io.spring.asciidoctor:spring-asciidoctor-extensions-section-ids")
api(project(":spring-boot-project:spring-boot-actuator"))
api(project(":spring-boot-project:spring-boot"))
api(project(":spring-boot-project:spring-boot-autoconfigure"))
@ -37,7 +37,7 @@ dependencies {
optional("io.dropwizard.metrics:metrics-jmx")
optional("io.lettuce:lettuce-core")
optional("io.micrometer:micrometer-observation")
optional("io.micrometer:micrometer-jakarta9")
optional("io.micrometer:micrometer-core")
optional("io.micrometer:micrometer-tracing")
optional("io.micrometer:micrometer-tracing-bridge-brave")
optional("io.micrometer:micrometer-tracing-bridge-otel")
@ -71,16 +71,13 @@ dependencies {
optional("io.zipkin.reporter2:zipkin-reporter-brave")
optional("io.zipkin.reporter2:zipkin-sender-urlconnection")
optional("io.opentelemetry:opentelemetry-exporter-zipkin")
optional("io.opentelemetry:opentelemetry-exporter-otlp")
optional("io.projectreactor.netty:reactor-netty-http")
optional("io.r2dbc:r2dbc-pool")
optional("io.r2dbc:r2dbc-proxy")
optional("io.r2dbc:r2dbc-spi")
optional("jakarta.jms:jakarta.jms-api")
optional("jakarta.persistence:jakarta.persistence-api")
optional("jakarta.servlet:jakarta.servlet-api")
optional("javax.cache:cache-api")
optional("org.apache.activemq:activemq-client-jakarta")
optional("org.apache.commons:commons-dbcp2") {
exclude group: "commons-logging", module: "commons-logging"
}
@ -108,7 +105,6 @@ dependencies {
optional("org.hibernate.validator:hibernate-validator")
optional("org.influxdb:influxdb-java")
optional("org.liquibase:liquibase-core") {
exclude group: "javax.activation", module: "javax.activation-api"
exclude group: "javax.xml.bind", module: "jaxb-api"
}
optional("org.mongodb:mongodb-driver-reactivestreams")
@ -162,7 +158,9 @@ dependencies {
testImplementation("org.assertj:assertj-core")
testImplementation("org.awaitility:awaitility")
testImplementation("org.cache2k:cache2k-api")
testImplementation("org.eclipse.jetty.ee10:jetty-ee10-webapp")
testImplementation("org.eclipse.jetty:jetty-webapp") {
exclude group: "org.eclipse.jetty.toolchain", module: "jetty-jakarta-servlet-api"
}
testImplementation("org.glassfish.jersey.ext:jersey-spring6")
testImplementation("org.glassfish.jersey.media:jersey-media-json-jackson")
testImplementation("org.hamcrest:hamcrest")

@ -53,6 +53,8 @@ class ReactiveCloudFoundrySecurityService {
private final String cloudControllerUrl;
private Mono<String> uaaUrl;
ReactiveCloudFoundrySecurityService(WebClient.Builder webClientBuilder, String cloudControllerUrl,
boolean skipSslValidation) {
Assert.notNull(webClientBuilder, "WebClient must not be null");
@ -147,7 +149,7 @@ class ReactiveCloudFoundrySecurityService {
* @return the UAA url Mono
*/
Mono<String> getUaaUrl() {
return this.webClient.get()
this.uaaUrl = this.webClient.get()
.uri(this.cloudControllerUrl + "/info")
.retrieve()
.bodyToMono(Map.class)
@ -155,6 +157,7 @@ class ReactiveCloudFoundrySecurityService {
.cache()
.onErrorMap((ex) -> new CloudFoundryAuthorizationException(Reason.SERVICE_UNAVAILABLE,
"Unable to fetch token keys from UAA."));
return this.uaaUrl;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -86,7 +86,7 @@ class CloudFoundrySecurityInterceptor {
return SecurityResponse.success();
}
private void check(HttpServletRequest request, EndpointId endpointId) {
private void check(HttpServletRequest request, EndpointId endpointId) throws Exception {
Token token = getToken(request);
this.tokenValidator.validate(token);
AccessLevel accessLevel = this.cloudFoundrySecurityService.getAccessLevel(token.toString(), this.applicationId);

@ -143,8 +143,11 @@ class OnAvailableEndpointCondition extends SpringBootCondition {
}
private Boolean isEnabledByDefault(Environment environment) {
Optional<Boolean> enabledByDefault = enabledByDefaultCache.computeIfAbsent(environment,
(ignore) -> Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class)));
Optional<Boolean> enabledByDefault = enabledByDefaultCache.get(environment);
if (enabledByDefault == null) {
enabledByDefault = Optional.ofNullable(environment.getProperty(ENABLED_BY_DEFAULT_KEY, Boolean.class));
enabledByDefaultCache.put(environment, enabledByDefault);
}
return enabledByDefault.orElse(null);
}

@ -150,7 +150,7 @@ public class IncludeExcludeEndpointFilter<E extends ExposableEndpoint<?>> implem
private final Set<EndpointId> endpointIds;
EndpointPatterns(String[] patterns) {
this((patterns != null) ? Arrays.asList(patterns) : null);
this((patterns != null) ? Arrays.asList(patterns) : (Collection<String>) null);
}
EndpointPatterns(Collection<String> patterns) {

@ -103,8 +103,7 @@ class JerseyWebEndpointManagementContextConfiguration {
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HEALTH_ENDPOINT_ID))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("No endpoint with id '%s' found".formatted(HEALTH_ENDPOINT_ID)));
.get();
return new JerseyAdditionalHealthEndpointPathsManagementResourcesRegistrar(health, healthEndpointGroups);
}

@ -120,8 +120,7 @@ public class WebFluxEndpointManagementContextConfiguration {
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID)));
.get();
return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), health,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}
@ -163,16 +162,16 @@ public class WebFluxEndpointManagementContextConfiguration {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ServerCodecConfigurer serverCodecConfigurer) {
process(serverCodecConfigurer);
if (bean instanceof ServerCodecConfigurer) {
process((ServerCodecConfigurer) bean);
}
return bean;
}
private void process(ServerCodecConfigurer configurer) {
for (HttpMessageWriter<?> writer : configurer.getWriters()) {
if (writer instanceof EncoderHttpMessageWriter<?> encoderHttpMessageWriter) {
process((encoderHttpMessageWriter).getEncoder());
if (writer instanceof EncoderHttpMessageWriter) {
process(((EncoderHttpMessageWriter<?>) writer).getEncoder());
}
}
}

@ -115,8 +115,7 @@ public class WebMvcEndpointManagementContextConfiguration {
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID)));
.get();
return new AdditionalHealthEndpointPathsWebMvcHandlerMapping(health,
groups.getAllWithAdditionalPath(WebServerNamespace.MANAGEMENT));
}
@ -158,8 +157,8 @@ public class WebMvcEndpointManagementContextConfiguration {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
configure(mappingJackson2HttpMessageConverter);
if (converter instanceof MappingJackson2HttpMessageConverter) {
configure((MappingJackson2HttpMessageConverter) converter);
}
}
}

@ -16,9 +16,12 @@
package org.springframework.boot.actuate.autoconfigure.health;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.BeanUtils;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
@ -36,6 +39,18 @@ public abstract class AbstractCompositeHealthContributorConfiguration<C, I exten
private final Function<B, I> indicatorFactory;
/**
* Creates a {@code AbstractCompositeHealthContributorConfiguration} that will use
* reflection to create health indicator instances.
* @deprecated since 3.0.0 in favor of
* {@link #AbstractCompositeHealthContributorConfiguration(Function)}
*/
@Deprecated(since = "3.0.0", forRemoval = true)
protected AbstractCompositeHealthContributorConfiguration() {
this.indicatorFactory = new ReflectionIndicatorFactory(
ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class, getClass()));
}
/**
* Creates a {@code AbstractCompositeHealthContributorConfiguration} that will use the
* given {@code indicatorFactory} to create health indicator instances.
@ -60,4 +75,34 @@ public abstract class AbstractCompositeHealthContributorConfiguration<C, I exten
return this.indicatorFactory.apply(bean);
}
private class ReflectionIndicatorFactory implements Function<B, I> {
private final Class<?> indicatorType;
private final Class<?> beanType;
ReflectionIndicatorFactory(ResolvableType type) {
this.indicatorType = type.resolveGeneric(1);
this.beanType = type.resolveGeneric(2);
}
@Override
public I apply(B bean) {
try {
return BeanUtils.instantiateClass(getConstructor(), bean);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create health indicator %s for bean type %s"
.formatted(this.indicatorType, this.beanType), ex);
}
}
@SuppressWarnings("unchecked")
private Constructor<I> getConstructor() throws NoSuchMethodException {
return (Constructor<I>) this.indicatorType.getDeclaredConstructor(this.beanType);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -36,6 +36,18 @@ import org.springframework.boot.actuate.health.HealthIndicator;
public abstract class CompositeHealthContributorConfiguration<I extends HealthIndicator, B>
extends AbstractCompositeHealthContributorConfiguration<HealthContributor, I, B> {
/**
* Creates a {@code CompositeHealthContributorConfiguration} that will use reflection
* to create {@link HealthIndicator} instances.
* @deprecated since 3.0.0 in favor of
* {@link #CompositeHealthContributorConfiguration(Function)}
*/
@SuppressWarnings("removal")
@Deprecated(since = "3.0.0", forRemoval = true)
public CompositeHealthContributorConfiguration() {
super();
}
/**
* Creates a {@code CompositeHealthContributorConfiguration} that will use the given
* {@code indicatorFactory} to create {@link HealthIndicator} instances.

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -36,6 +36,18 @@ import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
public abstract class CompositeReactiveHealthContributorConfiguration<I extends ReactiveHealthIndicator, B>
extends AbstractCompositeHealthContributorConfiguration<ReactiveHealthContributor, I, B> {
/**
* Creates a {@code CompositeReactiveHealthContributorConfiguration} that will use
* reflection to create {@link ReactiveHealthIndicator} instances.
* @deprecated since 3.0.0 in favor of
* {@link #CompositeReactiveHealthContributorConfiguration(Function)}
*/
@SuppressWarnings("removal")
@Deprecated(since = "3.0.0", forRemoval = true)
public CompositeReactiveHealthContributorConfiguration() {
super();
}
/**
* Creates a {@code CompositeReactiveHealthContributorConfiguration} that will use the
* given {@code indicatorFactory} to create {@link ReactiveHealthIndicator} instances.

@ -20,11 +20,9 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
@ -37,19 +35,16 @@ import org.springframework.boot.actuate.health.HealthEndpointGroupsPostProcessor
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.boot.actuate.health.HttpCodeStatusMapper;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.health.NamedContributors;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
import org.springframework.boot.actuate.health.SimpleHttpCodeStatusMapper;
import org.springframework.boot.actuate.health.SimpleStatusAggregator;
import org.springframework.boot.actuate.health.StatusAggregator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* Configuration for {@link HealthEndpoint} infrastructure beans.
@ -90,14 +85,6 @@ class HealthEndpointConfiguration {
return new AutoConfiguredHealthContributorRegistry(healthContributors, groups.getNames());
}
@Bean
@ConditionalOnProperty(name = "management.endpoint.health.validate-group-membership", havingValue = "true",
matchIfMissing = true)
HealthEndpointGroupMembershipValidator healthEndpointGroupMembershipValidator(HealthEndpointProperties properties,
HealthContributorRegistry healthContributorRegistry) {
return new HealthEndpointGroupMembershipValidator(properties, healthContributorRegistry);
}
@Bean
@ConditionalOnMissingBean
HealthEndpoint healthEndpoint(HealthContributorRegistry registry, HealthEndpointGroups groups,
@ -217,75 +204,4 @@ class HealthEndpointConfiguration {
}
/**
* {@link SmartInitializingSingleton} that validates health endpoint group membership,
* throwing a {@link NoSuchHealthContributorException} if an included or excluded
* contributor does not exist.
*/
static class HealthEndpointGroupMembershipValidator implements SmartInitializingSingleton {
private final HealthEndpointProperties properties;
private final HealthContributorRegistry registry;
HealthEndpointGroupMembershipValidator(HealthEndpointProperties properties,
HealthContributorRegistry registry) {
this.properties = properties;
this.registry = registry;
}
@Override
public void afterSingletonsInstantiated() {
validateGroups();
}
private void validateGroups() {
this.properties.getGroup().forEach((name, group) -> {
validate(group.getInclude(), "Included", name);
validate(group.getExclude(), "Excluded", name);
});
}
private void validate(Set<String> names, String type, String group) {
if (CollectionUtils.isEmpty(names)) {
return;
}
for (String name : names) {
if ("*".equals(name)) {
return;
}
String[] path = name.split("/");
if (!contributorExists(path)) {
throw new NoSuchHealthContributorException(type, name, group);
}
}
}
private boolean contributorExists(String[] path) {
int pathOffset = 0;
Object contributor = this.registry;
while (pathOffset < path.length) {
if (!(contributor instanceof NamedContributors)) {
return false;
}
contributor = ((NamedContributors<?>) contributor).getContributor(path[pathOffset]);
pathOffset++;
}
return (contributor != null);
}
/**
* Thrown when a contributor that does not exist is included in or excluded from a
* group.
*/
static class NoSuchHealthContributorException extends RuntimeException {
NoSuchHealthContributorException(String type, String name, String group) {
super(type + " health contributor '" + name + "' in group '" + group + "' does not exist");
}
}
}
}

@ -70,8 +70,7 @@ class HealthEndpointReactiveWebExtensionConfiguration {
ExposableWebEndpoint health = webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID)));
.get();
return new AdditionalHealthEndpointPathsWebFluxHandlerMapping(new EndpointMapping(""), health,
groups.getAllWithAdditionalPath(WebServerNamespace.SERVER));
}

@ -81,8 +81,7 @@ class HealthEndpointWebExtensionConfiguration {
return webEndpoints.stream()
.filter((endpoint) -> endpoint.getEndpointId().equals(HealthEndpoint.ID))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("No endpoint with id '%s' found".formatted(HealthEndpoint.ID)));
.get();
}
@ConditionalOnBean(DispatcherServlet.class)

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -37,16 +37,11 @@ import org.springframework.context.annotation.Bean;
*
* @author Eddú Meléndez
* @since 2.0.0
* @deprecated since 3.2.0 for removal in 3.4.0 in favor of the
* <a href="https://github.com/influxdata/influxdb-client-java">new client</a> and its own
* Spring Boot integration.
*/
@SuppressWarnings("removal")
@AutoConfiguration(after = InfluxDbAutoConfiguration.class)
@ConditionalOnClass(InfluxDB.class)
@ConditionalOnBean(InfluxDB.class)
@ConditionalOnEnabledHealthIndicator("influxdb")
@Deprecated(since = "3.2.0", forRemoval = true)
public class InfluxDbHealthContributorAutoConfiguration
extends CompositeHealthContributorConfiguration<InfluxDbHealthIndicator, InfluxDB> {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -29,7 +29,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration;
import org.springframework.context.annotation.Bean;
@ -39,7 +38,7 @@ import org.springframework.context.annotation.Bean;
* @author Stephane Nicoll
* @since 2.0.0
*/
@AutoConfiguration(after = { ActiveMQAutoConfiguration.class, ArtemisAutoConfiguration.class })
@AutoConfiguration(after = ArtemisAutoConfiguration.class)
@ConditionalOnClass(ConnectionFactory.class)
@ConditionalOnBean(ConnectionFactory.class)
@ConditionalOnEnabledHealthIndicator("jms")

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -32,7 +32,7 @@ public final class AutoTimeProperties {
private boolean enabled = true;
/**
* Whether to publish percentile histograms.
* Whether to publish percentile histrograms.
*/
private boolean percentilesHistogram;

@ -1,60 +0,0 @@
/*
* 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.metrics;
import io.micrometer.core.aop.CountedAspect;
import io.micrometer.core.aop.MeterTagAnnotationHandler;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.aspectj.weaver.Advice;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Micrometer-based metrics
* aspects.
*
* @author Jonatan Ivanov
* @since 3.2.0
*/
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
@ConditionalOnClass({ MeterRegistry.class, Advice.class })
@ConditionalOnBean(MeterRegistry.class)
public class MetricsAspectsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
@Bean
@ConditionalOnMissingBean
TimedAspect timedAspect(MeterRegistry registry,
ObjectProvider<MeterTagAnnotationHandler> meterTagAnnotationHandler) {
TimedAspect timedAspect = new TimedAspect(registry);
meterTagAnnotationHandler.ifAvailable(timedAspect::setMeterTagAnnotationHandler);
return timedAspect;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -79,8 +79,6 @@ public class MetricsProperties {
return this.enable;
}
@Deprecated(since = "3.2.0", forRemoval = true)
@DeprecatedConfigurationProperty(replacement = "management.observations.key-values", since = "3.2.0")
public Map<String, String> getTags() {
return this.tags;
}
@ -117,6 +115,8 @@ public class MetricsProperties {
public static class Client {
private final ClientRequest request = new ClientRequest();
/**
* Maximum number of unique URI tag values allowed. After the max number of
* tag values is reached, metrics with additional tag values are denied by
@ -124,6 +124,10 @@ public class MetricsProperties {
*/
private int maxUriTags = 100;
public ClientRequest getRequest() {
return this.request;
}
public int getMaxUriTags() {
return this.maxUriTags;
}
@ -132,10 +136,32 @@ public class MetricsProperties {
this.maxUriTags = maxUriTags;
}
public static class ClientRequest {
/**
* Name of the metric for sent requests.
*/
private String metricName = "http.client.requests";
@Deprecated(since = "3.0.0", forRemoval = true)
@DeprecatedConfigurationProperty(replacement = "management.observations.http.client.requests.name")
public String getMetricName() {
return this.metricName;
}
@Deprecated(since = "3.0.0", forRemoval = true)
public void setMetricName(String metricName) {
this.metricName = metricName;
}
}
}
public static class Server {
private final ServerRequest request = new ServerRequest();
/**
* Maximum number of unique URI tag values allowed. After the max number of
* tag values is reached, metrics with additional tag values are denied by
@ -143,6 +169,10 @@ public class MetricsProperties {
*/
private int maxUriTags = 100;
public ServerRequest getRequest() {
return this.request;
}
public int getMaxUriTags() {
return this.maxUriTags;
}
@ -151,6 +181,27 @@ public class MetricsProperties {
this.maxUriTags = maxUriTags;
}
public static class ServerRequest {
/**
* Name of the metric for received requests.
*/
private String metricName = "http.server.requests";
@Deprecated(since = "3.0.0", forRemoval = true)
@DeprecatedConfigurationProperty(replacement = "management.observations.http.server.requests.name")
public String getMetricName() {
return this.metricName;
}
@Deprecated(since = "3.0.0", forRemoval = true)
@DeprecatedConfigurationProperty(replacement = "management.observations.http.server.requests.name")
public void setMetricName(String metricName) {
this.metricName = metricName;
}
}
}
}

@ -50,7 +50,6 @@ public class PropertiesMeterFilter implements MeterFilter {
private final MeterFilter mapFilter;
@SuppressWarnings("removal")
public PropertiesMeterFilter(MetricsProperties properties) {
Assert.notNull(properties, "Properties must not be null");
this.properties = properties;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -140,12 +140,6 @@ public class DynatraceProperties extends StepRegistryProperties {
*/
private boolean useDynatraceSummaryInstruments = true;
/**
* Whether to export meter metadata (unit and description) to the Dynatrace
* backend.
*/
private boolean exportMeterMetadata = true;
public Map<String, String> getDefaultDimensions() {
return this.defaultDimensions;
}
@ -178,14 +172,6 @@ public class DynatraceProperties extends StepRegistryProperties {
this.useDynatraceSummaryInstruments = useDynatraceSummaryInstruments;
}
public boolean isExportMeterMetadata() {
return this.exportMeterMetadata;
}
public void setExportMeterMetadata(boolean exportMeterMetadata) {
this.exportMeterMetadata = exportMeterMetadata;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -95,11 +95,6 @@ class DynatracePropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapt
return get(v2(V2::isUseDynatraceSummaryInstruments), DynatraceConfig.super::useDynatraceSummaryInstruments);
}
@Override
public boolean exportMeterMetadata() {
return (get(v2(V2::isExportMeterMetadata), DynatraceConfig.super::exportMeterMetadata));
}
private <V> Function<DynatraceProperties, V> v1(Function<V1, V> getter) {
return (properties) -> getter.apply(properties.getV1());
}

@ -1,35 +0,0 @@
/*
* 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.metrics.export.otlp;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a OpenTelemetry Collector service.
*
* @author Eddú Meléndez
* @since 3.2.0
*/
public interface OtlpMetricsConnectionDetails extends ConnectionDetails {
/**
* Address to where metrics will be published.
* @return the address to where metrics will be published
*/
String getUrl();
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -24,7 +24,6 @@ import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegi
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -32,13 +31,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to OTLP.
*
* @author Eddú Meléndez
* @author Moritz Halbritter
* @since 3.0.0
*/
@AutoConfiguration(
@ -47,27 +44,19 @@ import org.springframework.core.env.Environment;
@ConditionalOnBean(Clock.class)
@ConditionalOnClass(OtlpMeterRegistry.class)
@ConditionalOnEnabledMetricsExport("otlp")
@EnableConfigurationProperties({ OtlpProperties.class, OpenTelemetryProperties.class })
@EnableConfigurationProperties(OtlpProperties.class)
public class OtlpMetricsExportAutoConfiguration {
private final OtlpProperties properties;
OtlpMetricsExportAutoConfiguration(OtlpProperties properties) {
public OtlpMetricsExportAutoConfiguration(OtlpProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean(OtlpMetricsConnectionDetails.class)
OtlpMetricsConnectionDetails otlpMetricsConnectionDetails() {
return new PropertiesOtlpMetricsConnectionDetails(this.properties);
}
@Bean
@ConditionalOnMissingBean
OtlpConfig otlpConfig(OpenTelemetryProperties openTelemetryProperties,
OtlpMetricsConnectionDetails connectionDetails, Environment environment) {
return new OtlpPropertiesConfigAdapter(this.properties, openTelemetryProperties, connectionDetails,
environment);
public OtlpConfig otlpConfig() {
return new OtlpPropertiesConfigAdapter(this.properties);
}
@Bean
@ -76,22 +65,4 @@ public class OtlpMetricsExportAutoConfiguration {
return new OtlpMeterRegistry(otlpConfig, clock);
}
/**
* Adapts {@link OtlpProperties} to {@link OtlpMetricsConnectionDetails}.
*/
static class PropertiesOtlpMetricsConnectionDetails implements OtlpMetricsConnectionDetails {
private final OtlpProperties properties;
PropertiesOtlpMetricsConnectionDetails(OtlpProperties properties) {
this.properties = properties;
}
@Override
public String getUrl() {
return this.properties.getUrl();
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -17,20 +17,15 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.micrometer.registry.otlp.AggregationTemporality;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
/**
* {@link ConfigurationProperties @ConfigurationProperties} for configuring OTLP metrics
* export.
*
* @author Eddú Meléndez
* @author Jonatan Ivanov
* @since 3.0.0
*/
@ConfigurationProperties(prefix = "management.otlp.metrics.export")
@ -41,27 +36,11 @@ public class OtlpProperties extends StepRegistryProperties {
*/
private String url = "http://localhost:4318/v1/metrics";
/**
* Aggregation temporality of sums. It defines the way additive values are expressed.
* This setting depends on the backend you use, some only support one temporality.
*/
private AggregationTemporality aggregationTemporality = AggregationTemporality.CUMULATIVE;
/**
* Monitored resource's attributes.
*/
private Map<String, String> resourceAttributes;
/**
* Headers for the exported metrics.
*/
private Map<String, String> headers;
/**
* Time unit for exported metrics.
*/
private TimeUnit baseTimeUnit = TimeUnit.MILLISECONDS;
public String getUrl() {
return this.url;
}
@ -70,39 +49,12 @@ public class OtlpProperties extends StepRegistryProperties {
this.url = url;
}
public AggregationTemporality getAggregationTemporality() {
return this.aggregationTemporality;
}
public void setAggregationTemporality(AggregationTemporality aggregationTemporality) {
this.aggregationTemporality = aggregationTemporality;
}
@Deprecated(since = "3.2.0", forRemoval = true)
@DeprecatedConfigurationProperty(replacement = "management.opentelemetry.resource-attributes", since = "3.2.0")
public Map<String, String> getResourceAttributes() {
return this.resourceAttributes;
}
@Deprecated(since = "3.2.0", forRemoval = true)
public void setResourceAttributes(Map<String, String> resourceAttributes) {
this.resourceAttributes = resourceAttributes;
}
public Map<String, String> getHeaders() {
return this.headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public TimeUnit getBaseTimeUnit() {
return this.baseTimeUnit;
}
public void setBaseTimeUnit(TimeUnit baseTimeUnit) {
this.baseTimeUnit = baseTimeUnit;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -16,45 +16,21 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.otlp;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import io.micrometer.registry.otlp.AggregationTemporality;
import io.micrometer.registry.otlp.OtlpConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.StepRegistryPropertiesConfigAdapter;
import org.springframework.boot.actuate.autoconfigure.opentelemetry.OpenTelemetryProperties;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
/**
* Adapter to convert {@link OtlpProperties} to an {@link OtlpConfig}.
*
* @author Eddú Meléndez
* @author Jonatan Ivanov
* @author Moritz Halbritter
*/
class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<OtlpProperties> implements OtlpConfig {
/**
* Default value for application name if {@code spring.application.name} is not set.
*/
private static final String DEFAULT_APPLICATION_NAME = "application";
private final OpenTelemetryProperties openTelemetryProperties;
private final OtlpMetricsConnectionDetails connectionDetails;
private final Environment environment;
OtlpPropertiesConfigAdapter(OtlpProperties properties, OpenTelemetryProperties openTelemetryProperties,
OtlpMetricsConnectionDetails connectionDetails, Environment environment) {
OtlpPropertiesConfigAdapter(OtlpProperties properties) {
super(properties);
this.connectionDetails = connectionDetails;
this.openTelemetryProperties = openTelemetryProperties;
this.environment = environment;
}
@Override
@ -64,36 +40,12 @@ class OtlpPropertiesConfigAdapter extends StepRegistryPropertiesConfigAdapter<Ot
@Override
public String url() {
return get((properties) -> this.connectionDetails.getUrl(), OtlpConfig.super::url);
}
@Override
public AggregationTemporality aggregationTemporality() {
return get(OtlpProperties::getAggregationTemporality, OtlpConfig.super::aggregationTemporality);
return get(OtlpProperties::getUrl, OtlpConfig.super::url);
}
@Override
@SuppressWarnings("removal")
public Map<String, String> resourceAttributes() {
Map<String, String> resourceAttributes = this.openTelemetryProperties.getResourceAttributes();
Map<String, String> result = new HashMap<>((!CollectionUtils.isEmpty(resourceAttributes)) ? resourceAttributes
: get(OtlpProperties::getResourceAttributes, OtlpConfig.super::resourceAttributes));
result.computeIfAbsent("service.name", (key) -> getApplicationName());
return Collections.unmodifiableMap(result);
}
private String getApplicationName() {
return this.environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
}
@Override
public Map<String, String> headers() {
return get(OtlpProperties::getHeaders, OtlpConfig.super::headers);
}
@Override
public TimeUnit baseTimeUnit() {
return get(OtlpProperties::getBaseTimeUnit, OtlpConfig.super::baseTimeUnit);
return get(OtlpProperties::getResourceAttributes, OtlpConfig.super::resourceAttributes);
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront;
import com.wavefront.sdk.common.clients.service.token.TokenService.Type;
import io.micrometer.wavefront.WavefrontConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.export.properties.PushRegistryPropertiesConfigAdapter;
@ -85,9 +84,4 @@ public class WavefrontPropertiesConfigAdapter
return get(Export::isReportDayDistribution, WavefrontConfig.super::reportDayDistribution);
}
@Override
public Type apiTokenType() {
return this.properties.getWavefrontApiTokenType();
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -29,9 +29,9 @@ import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Server;
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -57,12 +57,13 @@ import org.springframework.core.annotation.Order;
@ConditionalOnClass({ ResourceConfig.class, MetricsApplicationEventListener.class })
@ConditionalOnBean({ MeterRegistry.class, ResourceConfig.class })
@EnableConfigurationProperties(MetricsProperties.class)
@SuppressWarnings("removal")
public class JerseyServerMetricsAutoConfiguration {
private final ObservationProperties observationProperties;
private final MetricsProperties properties;
public JerseyServerMetricsAutoConfiguration(ObservationProperties observationProperties) {
this.observationProperties = observationProperties;
public JerseyServerMetricsAutoConfiguration(MetricsProperties properties) {
this.properties = properties;
}
@Bean
@ -74,19 +75,19 @@ public class JerseyServerMetricsAutoConfiguration {
@Bean
public ResourceConfigCustomizer jerseyServerMetricsResourceConfigCustomizer(MeterRegistry meterRegistry,
JerseyTagsProvider tagsProvider) {
String metricName = this.observationProperties.getHttp().getServer().getRequests().getName();
return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry, tagsProvider, metricName,
true, new AnnotationUtilsAnnotationFinder()));
Server server = this.properties.getWeb().getServer();
return (config) -> config.register(new MetricsApplicationEventListener(meterRegistry, tagsProvider,
server.getRequest().getMetricName(), true, new AnnotationUtilsAnnotationFinder()));
}
@Bean
@Order(0)
public MeterFilter jerseyMetricsUriTagFilter(MetricsProperties metricsProperties) {
String metricName = this.observationProperties.getHttp().getServer().getRequests().getName();
public MeterFilter jerseyMetricsUriTagFilter() {
String metricName = this.properties.getWeb().getServer().getRequest().getMetricName();
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
() -> String.format("Reached the maximum number of URI tags for '%s'.", metricName));
return MeterFilter.maximumAllowableTags(metricName, "uri",
metricsProperties.getWeb().getServer().getMaxUriTags(), filter);
return MeterFilter.maximumAllowableTags(metricName, "uri", this.properties.getWeb().getServer().getMaxUriTags(),
filter);
}
/**

@ -27,11 +27,9 @@ import io.micrometer.observation.ObservationFilter;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationPredicate;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.aop.ObservedAspect;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.handler.TracingAwareMeterObservationHandler;
import io.micrometer.tracing.handler.TracingObservationHandler;
import org.aspectj.weaver.Advice;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
@ -45,7 +43,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Observation API.
@ -53,7 +50,6 @@ import org.springframework.core.annotation.Order;
* @author Moritz Halbritter
* @author Brian Clozel
* @author Jonatan Ivanov
* @author Vedran Pavic
* @since 3.0.0
*/
@AutoConfiguration(after = { CompositeMeterRegistryAutoConfiguration.class, MicrometerTracingAutoConfiguration.class })
@ -79,12 +75,6 @@ public class ObservationAutoConfiguration {
return ObservationRegistry.create();
}
@Bean
@Order(0)
PropertiesObservationFilterPredicate propertiesObservationFilter(ObservationProperties properties) {
return new PropertiesObservationFilterPredicate(properties);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnMissingClass("io.micrometer.tracing.Tracer")
@ -152,16 +142,4 @@ public class ObservationAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class ObservedAspectConfiguration {
@Bean
@ConditionalOnMissingBean
ObservedAspect observedAspect(ObservationRegistry observationRegistry) {
return new ObservedAspect(observationRegistry);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -16,7 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.observation;
import java.util.ArrayList;
import java.util.List;
import io.micrometer.observation.ObservationHandler;
@ -31,7 +30,6 @@ import org.springframework.util.MultiValueMap;
* Groups {@link ObservationHandler ObservationHandlers} by type.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
@SuppressWarnings("rawtypes")
class ObservationHandlerGrouping {
@ -48,14 +46,13 @@ class ObservationHandlerGrouping {
void apply(List<ObservationHandler<?>> handlers, ObservationConfig config) {
MultiValueMap<Class<? extends ObservationHandler>, ObservationHandler<?>> groupings = new LinkedMultiValueMap<>();
List<ObservationHandler<?>> handlersWithoutCategory = new ArrayList<>();
for (ObservationHandler<?> handler : handlers) {
Class<? extends ObservationHandler> category = findCategory(handler);
if (category != null) {
groupings.add(category, handler);
}
else {
handlersWithoutCategory.add(handler);
config.observationHandler(handler);
}
}
for (Class<? extends ObservationHandler> category : this.categories) {
@ -64,9 +61,6 @@ class ObservationHandlerGrouping {
config.observationHandler(new FirstMatchingCompositeObservationHandler(handlerGroup));
}
}
for (ObservationHandler<?> observationHandler : handlersWithoutCategory) {
config.observationHandler(observationHandler);
}
}
private Class<? extends ObservationHandler> findCategory(ObservationHandler<?> handler) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -16,9 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.observation;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@ -26,7 +23,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* observations.
*
* @author Brian Clozel
* @author Moritz Halbritter
* @since 3.0.0
*/
@ConfigurationProperties("management.observations")
@ -34,37 +30,10 @@ public class ObservationProperties {
private final Http http = new Http();
/**
* Common key-values that are applied to every observation.
*/
private Map<String, String> keyValues = new LinkedHashMap<>();
/**
* Whether observations starting with the specified name should be enabled. The
* longest match wins, the key 'all' can also be used to configure all observations.
*/
private Map<String, Boolean> enable = new LinkedHashMap<>();
public Map<String, Boolean> getEnable() {
return this.enable;
}
public void setEnable(Map<String, Boolean> enable) {
this.enable = enable;
}
public Http getHttp() {
return this.http;
}
public Map<String, String> getKeyValues() {
return this.keyValues;
}
public void setKeyValues(Map<String, String> keyValues) {
this.keyValues = keyValues;
}
public static class Http {
private final Client client = new Client();
@ -90,9 +59,10 @@ public class ObservationProperties {
public static class ClientRequests {
/**
* Name of the observation for client requests.
* Name of the observation for client requests. If empty, will use the
* default "http.client.requests".
*/
private String name = "http.client.requests";
private String name;
public String getName() {
return this.name;
@ -117,9 +87,10 @@ public class ObservationProperties {
public static class ServerRequests {
/**
* Name of the observation for server requests.
* Name of the observation for server requests. If empty, will use the
* default "http.server.requests".
*/
private String name = "http.server.requests";
private String name;
public String getName() {
return this.name;

@ -1,83 +0,0 @@
/*
* 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.observation;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Supplier;
import io.micrometer.common.KeyValues;
import io.micrometer.observation.Observation.Context;
import io.micrometer.observation.ObservationFilter;
import io.micrometer.observation.ObservationPredicate;
import org.springframework.util.StringUtils;
/**
* {@link ObservationFilter} to apply settings from {@link ObservationProperties}.
*
* @author Moritz Halbritter
*/
class PropertiesObservationFilterPredicate implements ObservationFilter, ObservationPredicate {
private final ObservationFilter commonKeyValuesFilter;
private final ObservationProperties properties;
PropertiesObservationFilterPredicate(ObservationProperties properties) {
this.properties = properties;
this.commonKeyValuesFilter = createCommonKeyValuesFilter(properties);
}
@Override
public Context map(Context context) {
return this.commonKeyValuesFilter.map(context);
}
@Override
public boolean test(String name, Context context) {
return lookupWithFallbackToAll(this.properties.getEnable(), name, true);
}
private static <T> T lookupWithFallbackToAll(Map<String, T> values, String name, T defaultValue) {
if (values.isEmpty()) {
return defaultValue;
}
return doLookup(values, name, () -> values.getOrDefault("all", defaultValue));
}
private static <T> T doLookup(Map<String, T> values, String name, Supplier<T> defaultValue) {
while (StringUtils.hasLength(name)) {
T result = values.get(name);
if (result != null) {
return result;
}
int lastDot = name.lastIndexOf('.');
name = (lastDot != -1) ? name.substring(0, lastDot) : "";
}
return defaultValue.get();
}
private static ObservationFilter createCommonKeyValuesFilter(ObservationProperties properties) {
if (properties.getKeyValues().isEmpty()) {
return (context) -> context;
}
KeyValues keyValues = KeyValues.of(properties.getKeyValues().entrySet(), Entry::getKey, Entry::getValue);
return (context) -> context.addLowCardinalityKeyValues(keyValues);
}
}

@ -43,6 +43,7 @@ import org.springframework.graphql.observation.GraphQlObservationInstrumentation
@AutoConfiguration(after = ObservationAutoConfiguration.class)
@ConditionalOnBean(ObservationRegistry.class)
@ConditionalOnClass({ GraphQL.class, GraphQlSource.class, Observation.class })
@SuppressWarnings("removal")
public class GraphQlObservationAutoConfiguration {
@Bean

@ -1,72 +0,0 @@
/*
* 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.observation.jms;
import io.micrometer.jakarta9.instrument.jms.JmsPublishObservationContext;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import jakarta.jms.Message;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for instrumenting
* {@link JmsTemplate} beans for Observability.
*
* @author Brian Clozel
* @since 3.2.0
*/
@AutoConfiguration(after = { JmsAutoConfiguration.class, ObservationAutoConfiguration.class })
@ConditionalOnBean({ ObservationRegistry.class, JmsTemplate.class })
@ConditionalOnClass({ Observation.class, Message.class, JmsTemplate.class, JmsPublishObservationContext.class })
public class JmsTemplateObservationAutoConfiguration {
@Bean
static JmsTemplateObservationPostProcessor jmsTemplateObservationPostProcessor(
ObjectProvider<ObservationRegistry> observationRegistry) {
return new JmsTemplateObservationPostProcessor(observationRegistry);
}
static class JmsTemplateObservationPostProcessor implements BeanPostProcessor {
private final ObjectProvider<ObservationRegistry> observationRegistry;
JmsTemplateObservationPostProcessor(ObjectProvider<ObservationRegistry> observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JmsTemplate jmsTemplate) {
this.observationRegistry.ifAvailable(jmsTemplate::setObservationRegistry);
}
return bean;
}
}
}

@ -1,20 +0,0 @@
/*
* 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.
*/
/**
* Auto-configuration for JMS observations.
*/
package org.springframework.boot.actuate.autoconfigure.observation.jms;

@ -0,0 +1,62 @@
/*
* Copyright 2012-2022 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.observation.web.client;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
import org.springframework.http.client.observation.ClientRequestObservationContext;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
/**
* Adapter class that applies {@link RestTemplateExchangeTagsProvider} tags as a
* {@link ClientRequestObservationConvention}.
*
* @author Brian Clozel
*/
@SuppressWarnings({ "removal" })
class ClientHttpObservationConventionAdapter implements ClientRequestObservationConvention {
private final String metricName;
private final RestTemplateExchangeTagsProvider tagsProvider;
ClientHttpObservationConventionAdapter(String metricName, RestTemplateExchangeTagsProvider tagsProvider) {
this.metricName = metricName;
this.tagsProvider = tagsProvider;
}
@Override
@SuppressWarnings("deprecation")
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
Iterable<Tag> tags = this.tagsProvider.getTags(context.getUriTemplate(), context.getCarrier(),
context.getResponse());
return KeyValues.of(tags, Tag::getKey, Tag::getValue);
}
@Override
public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) {
return KeyValues.empty();
}
@Override
public String getName() {
return this.metricName;
}
}

@ -0,0 +1,76 @@
/*
* Copyright 2012-2022 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.observation.web.client;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import io.micrometer.observation.Observation;
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
import org.springframework.core.Conventions;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientRequestObservationContext;
import org.springframework.web.reactive.function.client.ClientRequestObservationConvention;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Adapter class that applies {@link WebClientExchangeTagsProvider} tags as a
* {@link ClientRequestObservationConvention}.
*
* @author Brian Clozel
*/
@SuppressWarnings("removal")
class ClientObservationConventionAdapter implements ClientRequestObservationConvention {
private static final String URI_TEMPLATE_ATTRIBUTE = Conventions.getQualifiedAttributeName(WebClient.class,
"uriTemplate");
private final String metricName;
private final WebClientExchangeTagsProvider tagsProvider;
ClientObservationConventionAdapter(String metricName, WebClientExchangeTagsProvider tagsProvider) {
this.metricName = metricName;
this.tagsProvider = tagsProvider;
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof ClientRequestObservationContext;
}
@Override
public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) {
ClientRequest request = context.getRequest();
if (request == null) {
request = context.getCarrier().attribute(URI_TEMPLATE_ATTRIBUTE, context.getUriTemplate()).build();
}
Iterable<Tag> tags = this.tagsProvider.tags(request, context.getResponse(), context.getError());
return KeyValues.of(tags, Tag::getKey, Tag::getValue);
}
@Override
public KeyValues getHighCardinalityKeyValues(ClientRequestObservationContext context) {
return KeyValues.empty();
}
@Override
public String getName() {
return this.metricName;
}
}

@ -65,10 +65,13 @@ public class HttpClientObservationsAutoConfiguration {
@Bean
@Order(0)
@SuppressWarnings("removal")
MeterFilter metricsHttpClientUriTagFilter(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
Client clientProperties = metricsProperties.getWeb().getClient();
String name = observationProperties.getHttp().getClient().getRequests().getName();
String metricName = clientProperties.getRequest().getMetricName();
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
String name = (observationName != null) ? observationName : metricName;
MeterFilter denyFilter = new OnlyOnceLoggingDenyMeterFilter(
() -> "Reached the maximum number of URI tags for '%s'. Are you using 'uriVariables'?"
.formatted(name));

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -22,6 +22,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.actuate.metrics.web.client.ObservationRestTemplateCustomizer;
import org.springframework.boot.actuate.metrics.web.client.RestTemplateExchangeTagsProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.client.RestTemplateBuilder;
@ -39,16 +40,39 @@ import org.springframework.web.client.RestTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(RestTemplateBuilder.class)
@SuppressWarnings("removal")
class RestTemplateObservationConfiguration {
@Bean
ObservationRestTemplateCustomizer observationRestTemplateCustomizer(ObservationRegistry observationRegistry,
ObjectProvider<ClientRequestObservationConvention> customConvention,
ObservationProperties observationProperties, MetricsProperties metricsProperties) {
String name = observationProperties.getHttp().getClient().getRequests().getName();
ClientRequestObservationConvention observationConvention = customConvention
.getIfAvailable(() -> new DefaultClientRequestObservationConvention(name));
ObservationProperties observationProperties, MetricsProperties metricsProperties,
ObjectProvider<RestTemplateExchangeTagsProvider> optionalTagsProvider) {
String name = observationName(observationProperties, metricsProperties);
ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(),
name, optionalTagsProvider.getIfAvailable());
return new ObservationRestTemplateCustomizer(observationRegistry, observationConvention);
}
private static String observationName(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
return (observationName != null) ? observationName : metricName;
}
private static ClientRequestObservationConvention createConvention(
ClientRequestObservationConvention customConvention, String name,
RestTemplateExchangeTagsProvider tagsProvider) {
if (customConvention != null) {
return customConvention;
}
else if (tagsProvider != null) {
return new ClientHttpObservationConventionAdapter(name, tagsProvider);
}
else {
return new DefaultClientRequestObservationConvention(name);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -22,6 +22,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.actuate.metrics.web.reactive.client.ObservationWebClientCustomizer;
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -36,16 +37,39 @@ import org.springframework.web.reactive.function.client.WebClient;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebClient.class)
@SuppressWarnings("removal")
class WebClientObservationConfiguration {
@Bean
ObservationWebClientCustomizer observationWebClientCustomizer(ObservationRegistry observationRegistry,
ObjectProvider<ClientRequestObservationConvention> customConvention,
ObservationProperties observationProperties, MetricsProperties metricsProperties) {
String name = observationProperties.getHttp().getClient().getRequests().getName();
ClientRequestObservationConvention observationConvention = customConvention
.getIfAvailable(() -> new DefaultClientRequestObservationConvention(name));
ObservationProperties observationProperties, ObjectProvider<WebClientExchangeTagsProvider> tagsProvider,
MetricsProperties metricsProperties) {
String name = observationName(observationProperties, metricsProperties);
ClientRequestObservationConvention observationConvention = createConvention(customConvention.getIfAvailable(),
tagsProvider.getIfAvailable(), name);
return new ObservationWebClientCustomizer(observationRegistry, observationConvention);
}
private static ClientRequestObservationConvention createConvention(
ClientRequestObservationConvention customConvention, WebClientExchangeTagsProvider tagsProvider,
String name) {
if (customConvention != null) {
return customConvention;
}
else if (tagsProvider != null) {
return new ClientObservationConventionAdapter(name, tagsProvider);
}
else {
return new DefaultClientRequestObservationConvention(name);
}
}
private static String observationName(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
String metricName = metricsProperties.getWeb().getClient().getRequest().getMetricName();
String observationName = observationProperties.getHttp().getClient().getRequests().getName();
return (observationName != null) ? observationName : metricName;
}
}

@ -0,0 +1,79 @@
/*
* Copyright 2012-2022 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.observation.web.reactive;
import java.util.List;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import org.springframework.boot.actuate.metrics.web.reactive.server.DefaultWebFluxTagsProvider;
import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor;
import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.observation.ServerRequestObservationContext;
import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
/**
* Adapter class that applies {@link WebFluxTagsProvider} tags as a
* {@link ServerRequestObservationConvention}.
*
* @author Brian Clozel
*/
@SuppressWarnings("removal")
@Deprecated(since = "3.0.0", forRemoval = true)
class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention {
private final WebSessionManager webSessionManager = new DefaultWebSessionManager();
private final ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create();
private final LocaleContextResolver localeContextResolver = new AcceptHeaderLocaleContextResolver();
private final String name;
private final WebFluxTagsProvider tagsProvider;
ServerRequestObservationConventionAdapter(String name, WebFluxTagsProvider tagsProvider) {
this.name = name;
this.tagsProvider = tagsProvider;
}
ServerRequestObservationConventionAdapter(String name, List<WebFluxTagsContributor> contributors) {
this(name, new DefaultWebFluxTagsProvider(contributors));
}
@Override
public String getName() {
return this.name;
}
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
DefaultServerWebExchange serverWebExchange = new DefaultServerWebExchange(context.getCarrier(),
context.getResponse(), this.webSessionManager, this.serverCodecConfigurer, this.localeContextResolver);
serverWebExchange.getAttributes().putAll(context.getAttributes());
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(serverWebExchange, context.getError());
return KeyValues.of(tags, Tag::getKey, Tag::getValue);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -16,24 +16,37 @@
package org.springframework.boot.actuate.autoconfigure.observation.web.reactive;
import java.util.List;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsContributor;
import org.springframework.boot.actuate.metrics.web.reactive.server.WebFluxTagsProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.observation.DefaultServerRequestObservationConvention;
import org.springframework.http.server.reactive.observation.ServerRequestObservationConvention;
import org.springframework.web.filter.reactive.ServerHttpObservationFilter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for instrumentation of Spring
@ -44,18 +57,69 @@ import org.springframework.core.annotation.Order;
* @author Dmytro Nosan
* @since 3.0.0
*/
@AutoConfiguration(after = { SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class })
@ConditionalOnClass({ Observation.class, MeterRegistry.class })
@ConditionalOnBean({ ObservationRegistry.class, MeterRegistry.class })
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class, ObservationAutoConfiguration.class })
@ConditionalOnClass(Observation.class)
@ConditionalOnBean(ObservationRegistry.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class })
@SuppressWarnings("removal")
public class WebFluxObservationAutoConfiguration {
private final MetricsProperties metricsProperties;
private final ObservationProperties observationProperties;
public WebFluxObservationAutoConfiguration(MetricsProperties metricsProperties,
ObservationProperties observationProperties) {
this.metricsProperties = metricsProperties;
this.observationProperties = observationProperties;
}
@Bean
@ConditionalOnMissingBean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public ServerHttpObservationFilter webfluxObservationFilter(ObservationRegistry registry,
ObjectProvider<ServerRequestObservationConvention> customConvention,
ObjectProvider<WebFluxTagsProvider> tagConfigurer,
ObjectProvider<WebFluxTagsContributor> contributorsProvider) {
String observationName = this.observationProperties.getHttp().getServer().getRequests().getName();
String metricName = this.metricsProperties.getWeb().getServer().getRequest().getMetricName();
String name = (observationName != null) ? observationName : metricName;
WebFluxTagsProvider tagsProvider = tagConfigurer.getIfAvailable();
List<WebFluxTagsContributor> tagsContributors = contributorsProvider.orderedStream().toList();
ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name,
tagsProvider, tagsContributors);
return new ServerHttpObservationFilter(registry, convention);
}
private static ServerRequestObservationConvention createConvention(
ServerRequestObservationConvention customConvention, String name, WebFluxTagsProvider tagsProvider,
List<WebFluxTagsContributor> tagsContributors) {
if (customConvention != null) {
return customConvention;
}
if (tagsProvider != null) {
return new ServerRequestObservationConventionAdapter(name, tagsProvider);
}
if (!tagsContributors.isEmpty()) {
return new ServerRequestObservationConventionAdapter(name, tagsContributors);
}
return new DefaultServerRequestObservationConvention(name);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnBean(MeterRegistry.class)
static class MeterFilterConfiguration {
@Bean
@Order(0)
MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties,
ObservationProperties observationProperties) {
String name = observationProperties.getHttp().getServer().getRequests().getName();
String observationName = observationProperties.getHttp().getServer().getRequests().getName();
String name = (observationName != null) ? observationName
: metricsProperties.getWeb().getServer().getRequest().getMetricName();
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
() -> "Reached the maximum number of URI tags for '%s'.".formatted(name));
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),
@ -63,3 +127,5 @@ public class WebFluxObservationAutoConfiguration {
}
}
}

@ -0,0 +1,76 @@
/*
* Copyright 2012-2022 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.observation.web.servlet;
import java.util.List;
import io.micrometer.common.KeyValues;
import io.micrometer.core.instrument.Tag;
import io.micrometer.observation.Observation;
import org.springframework.boot.actuate.metrics.web.servlet.DefaultWebMvcTagsProvider;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
import org.springframework.http.server.observation.ServerRequestObservationContext;
import org.springframework.http.server.observation.ServerRequestObservationConvention;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerMapping;
/**
* Adapter class that applies {@link WebMvcTagsProvider} tags as a
* {@link ServerRequestObservationConvention}.
*
* @author Brian Clozel
*/
@SuppressWarnings("removal")
@Deprecated(since = "3.0.0", forRemoval = true)
class ServerRequestObservationConventionAdapter implements ServerRequestObservationConvention {
private final String observationName;
private final WebMvcTagsProvider tagsProvider;
ServerRequestObservationConventionAdapter(String observationName, WebMvcTagsProvider tagsProvider,
List<WebMvcTagsContributor> contributors) {
Assert.state((tagsProvider != null) || (contributors != null),
"adapter should adapt to a WebMvcTagsProvider or a list of contributors");
this.observationName = observationName;
this.tagsProvider = (tagsProvider != null) ? tagsProvider : new DefaultWebMvcTagsProvider(contributors);
}
@Override
public String getName() {
return this.observationName;
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof ServerRequestObservationContext;
}
@Override
public KeyValues getLowCardinalityKeyValues(ServerRequestObservationContext context) {
Iterable<Tag> tags = this.tagsProvider.getTags(context.getCarrier(), context.getResponse(), getHandler(context),
context.getError());
return KeyValues.of(tags, Tag::getKey, Tag::getValue);
}
private Object getHandler(ServerRequestObservationContext context) {
return context.getCarrier().getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -16,6 +16,8 @@
package org.springframework.boot.actuate.autoconfigure.observation.web.servlet;
import java.util.List;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.observation.Observation;
@ -30,6 +32,8 @@ import org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDen
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationProperties;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsContributor;
import org.springframework.boot.actuate.metrics.web.servlet.WebMvcTagsProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -62,16 +66,28 @@ import org.springframework.web.servlet.DispatcherServlet;
@ConditionalOnClass({ DispatcherServlet.class, Observation.class })
@ConditionalOnBean(ObservationRegistry.class)
@EnableConfigurationProperties({ MetricsProperties.class, ObservationProperties.class })
@SuppressWarnings("removal")
public class WebMvcObservationAutoConfiguration {
private final MetricsProperties metricsProperties;
private final ObservationProperties observationProperties;
public WebMvcObservationAutoConfiguration(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
this.observationProperties = observationProperties;
this.metricsProperties = metricsProperties;
}
@Bean
@ConditionalOnMissingFilterBean
public FilterRegistrationBean<ServerHttpObservationFilter> webMvcObservationFilter(ObservationRegistry registry,
ObjectProvider<ServerRequestObservationConvention> customConvention,
ObservationProperties observationProperties) {
String name = observationProperties.getHttp().getServer().getRequests().getName();
ServerRequestObservationConvention convention = customConvention
.getIfAvailable(() -> new DefaultServerRequestObservationConvention(name));
ObjectProvider<WebMvcTagsProvider> customTagsProvider,
ObjectProvider<WebMvcTagsContributor> contributorsProvider) {
String name = httpRequestsMetricName(this.observationProperties, this.metricsProperties);
ServerRequestObservationConvention convention = createConvention(customConvention.getIfAvailable(), name,
customTagsProvider.getIfAvailable(), contributorsProvider.orderedStream().toList());
ServerHttpObservationFilter filter = new ServerHttpObservationFilter(registry, convention);
FilterRegistrationBean<ServerHttpObservationFilter> registration = new FilterRegistrationBean<>(filter);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
@ -79,6 +95,27 @@ public class WebMvcObservationAutoConfiguration {
return registration;
}
private static ServerRequestObservationConvention createConvention(
ServerRequestObservationConvention customConvention, String name, WebMvcTagsProvider tagsProvider,
List<WebMvcTagsContributor> contributors) {
if (customConvention != null) {
return customConvention;
}
else if (tagsProvider != null || contributors.size() > 0) {
return new ServerRequestObservationConventionAdapter(name, tagsProvider, contributors);
}
else {
return new DefaultServerRequestObservationConvention(name);
}
}
private static String httpRequestsMetricName(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
String observationName = observationProperties.getHttp().getServer().getRequests().getName();
return (observationName != null) ? observationName
: metricsProperties.getWeb().getServer().getRequest().getMetricName();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
@ConditionalOnBean(MeterRegistry.class)
@ -86,9 +123,9 @@ public class WebMvcObservationAutoConfiguration {
@Bean
@Order(0)
MeterFilter metricsHttpServerUriTagFilter(ObservationProperties observationProperties,
MetricsProperties metricsProperties) {
String name = observationProperties.getHttp().getServer().getRequests().getName();
MeterFilter metricsHttpServerUriTagFilter(MetricsProperties metricsProperties,
ObservationProperties observationProperties) {
String name = httpRequestsMetricName(observationProperties, metricsProperties);
MeterFilter filter = new OnlyOnceLoggingDenyMeterFilter(
() -> String.format("Reached the maximum number of URI tags for '%s'.", name));
return MeterFilter.maximumAllowableTags(name, "uri", metricsProperties.getWeb().getServer().getMaxUriTags(),

@ -1,86 +0,0 @@
/*
* 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.opentelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@AutoConfiguration
@ConditionalOnClass(OpenTelemetrySdk.class)
@EnableConfigurationProperties(OpenTelemetryProperties.class)
public class OpenTelemetryAutoConfiguration {
/**
* Default value for application name if {@code spring.application.name} is not set.
*/
private static final String DEFAULT_APPLICATION_NAME = "application";
static final AttributeKey<String> ATTRIBUTE_KEY_SERVICE_NAME = AttributeKey.stringKey("service.name");
@Bean
@ConditionalOnMissingBean(OpenTelemetry.class)
OpenTelemetrySdk openTelemetry(ObjectProvider<SdkTracerProvider> tracerProvider,
ObjectProvider<ContextPropagators> propagators, ObjectProvider<SdkLoggerProvider> loggerProvider,
ObjectProvider<SdkMeterProvider> meterProvider) {
OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
tracerProvider.ifAvailable(builder::setTracerProvider);
propagators.ifAvailable(builder::setPropagators);
loggerProvider.ifAvailable(builder::setLoggerProvider);
meterProvider.ifAvailable(builder::setMeterProvider);
return builder.build();
}
@Bean
@ConditionalOnMissingBean
Resource openTelemetryResource(Environment environment, OpenTelemetryProperties properties) {
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
return Resource.getDefault()
.merge(Resource.create(Attributes.of(ATTRIBUTE_KEY_SERVICE_NAME, applicationName)))
.merge(toResource(properties));
}
private static Resource toResource(OpenTelemetryProperties properties) {
ResourceBuilder builder = Resource.builder();
properties.getResourceAttributes().forEach(builder::put);
return builder.build();
}
}

@ -1,46 +0,0 @@
/*
* 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.opentelemetry;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for OpenTelemetry.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@ConfigurationProperties(prefix = "management.opentelemetry")
public class OpenTelemetryProperties {
/**
* Resource attributes.
*/
private Map<String, String> resourceAttributes = new HashMap<>();
public Map<String, String> getResourceAttributes() {
return this.resourceAttributes;
}
public void setResourceAttributes(Map<String, String> resourceAttributes) {
this.resourceAttributes = resourceAttributes;
}
}

@ -1,20 +0,0 @@
/*
* 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.
*/
/**
* Auto-configuration for OpenTelemetry.
*/
package org.springframework.boot.actuate.autoconfigure.opentelemetry;

@ -1,87 +0,0 @@
/*
* 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.r2dbc;
import io.micrometer.observation.ObservationRegistry;
import io.r2dbc.proxy.ProxyConnectionFactory;
import io.r2dbc.proxy.observation.ObservationProxyExecutionListener;
import io.r2dbc.proxy.observation.QueryObservationConvention;
import io.r2dbc.proxy.observation.QueryParametersTagProvider;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.r2dbc.ConnectionFactoryDecorator;
import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory;
import org.springframework.context.annotation.Bean;
/**
* {@link EnableAutoConfiguration Auto-configuration} for R2DBC observability support.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@AutoConfiguration(after = ObservationAutoConfiguration.class)
@ConditionalOnClass({ ConnectionFactory.class, ProxyConnectionFactory.class })
@EnableConfigurationProperties(R2dbcObservationProperties.class)
public class R2dbcObservationAutoConfiguration {
@Bean
@ConditionalOnBean(ObservationRegistry.class)
ConnectionFactoryDecorator connectionFactoryDecorator(R2dbcObservationProperties properties,
ObservationRegistry observationRegistry,
ObjectProvider<QueryObservationConvention> queryObservationConvention,
ObjectProvider<QueryParametersTagProvider> queryParametersTagProvider) {
return (connectionFactory) -> {
HostAndPort hostAndPort = extractHostAndPort(connectionFactory);
ObservationProxyExecutionListener listener = new ObservationProxyExecutionListener(observationRegistry,
connectionFactory, hostAndPort.host(), hostAndPort.port());
listener.setIncludeParameterValues(properties.isIncludeParameterValues());
queryObservationConvention.ifAvailable(listener::setQueryObservationConvention);
queryParametersTagProvider.ifAvailable(listener::setQueryParametersTagProvider);
return ProxyConnectionFactory.builder(connectionFactory).listener(listener).build();
};
}
private HostAndPort extractHostAndPort(ConnectionFactory connectionFactory) {
OptionsCapableConnectionFactory optionsCapableConnectionFactory = OptionsCapableConnectionFactory
.unwrapFrom(connectionFactory);
if (optionsCapableConnectionFactory == null) {
return HostAndPort.empty();
}
ConnectionFactoryOptions options = optionsCapableConnectionFactory.getOptions();
Object host = options.getValue(ConnectionFactoryOptions.HOST);
Object port = options.getValue(ConnectionFactoryOptions.PORT);
if (!(host instanceof String hostAsString) || !(port instanceof Integer portAsInt)) {
return HostAndPort.empty();
}
return new HostAndPort(hostAsString, portAsInt);
}
private record HostAndPort(String host, Integer port) {
static HostAndPort empty() {
return new HostAndPort(null, null);
}
}
}

@ -1,43 +0,0 @@
/*
* 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.r2dbc;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for R2DBC observability.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@ConfigurationProperties("management.observations.r2dbc")
public class R2dbcObservationProperties {
/**
* Whether to tag actual query parameter values.
*/
private boolean includeParameterValues;
public boolean isIncludeParameterValues() {
return this.includeParameterValues;
}
public void setIncludeParameterValues(boolean includeParameterValues) {
this.includeParameterValues = includeParameterValues;
}
}

@ -1,63 +0,0 @@
/*
* 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.scheduling;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
/**
* {@link EnableAutoConfiguration Auto-configuration} to enable observability for
* scheduled tasks.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@AutoConfiguration(after = ObservationAutoConfiguration.class)
@ConditionalOnBean(ObservationRegistry.class)
@ConditionalOnClass(ThreadPoolTaskScheduler.class)
public class ScheduledTasksObservabilityAutoConfiguration {
@Bean
ObservabilitySchedulingConfigurer observabilitySchedulingConfigurer(ObservationRegistry observationRegistry) {
return new ObservabilitySchedulingConfigurer(observationRegistry);
}
static final class ObservabilitySchedulingConfigurer implements SchedulingConfigurer {
private final ObservationRegistry observationRegistry;
ObservabilitySchedulingConfigurer(ObservationRegistry observationRegistry) {
this.observationRegistry = observationRegistry;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setObservationRegistry(this.observationRegistry);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -29,6 +29,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.Re
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
@ -37,8 +38,6 @@ import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.cors.reactive.PreFlightRequestHandler;
import org.springframework.web.cors.reactive.PreFlightRequestWebFilter;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Reactive Spring Security when
* actuator is on the classpath. Specifically, it permits access to the health endpoint
@ -64,8 +63,8 @@ public class ReactiveManagementWebSecurityAutoConfiguration {
});
PreFlightRequestWebFilter filter = new PreFlightRequestWebFilter(handler);
http.addFilterAt(filter, SecurityWebFiltersOrder.CORS);
http.httpBasic(withDefaults());
http.formLogin(withDefaults());
http.httpBasic(Customizer.withDefaults());
http.formLogin(Customizer.withDefaults());
return http.build();
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -31,12 +31,11 @@ import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAu
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.ClassUtils;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Security when actuator is
* on the classpath. It allows unauthenticated access to the {@link HealthEndpoint}. If
@ -64,10 +63,10 @@ public class ManagementWebSecurityAutoConfiguration {
requests.anyRequest().authenticated();
});
if (ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) {
http.cors(withDefaults());
http.cors();
}
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
http.formLogin(Customizer.withDefaults());
http.httpBasic(Customizer.withDefaults());
return http.build();
}

@ -54,14 +54,12 @@ import io.micrometer.tracing.exporter.SpanReporter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties.Baggage.Correlation;
import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties.Propagation.PropagationType;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.IncompatibleConfigurationException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@ -73,12 +71,12 @@ import org.springframework.core.env.Environment;
*
* @author Moritz Halbritter
* @author Marcin Grzejszczak
* @author Jonatan Ivanov
* @since 3.0.0
*/
@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class)
@ConditionalOnClass({ Tracer.class, BraveTracer.class })
@EnableConfigurationProperties(TracingProperties.class)
@ConditionalOnEnabledTracing
public class BraveAutoConfiguration {
private static final BraveBaggageManager BRAVE_BAGGAGE_MANAGER = new BraveBaggageManager();
@ -99,31 +97,14 @@ public class BraveAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public Tracing braveTracing(Environment environment, TracingProperties properties, List<SpanHandler> spanHandlers,
public Tracing braveTracing(Environment environment, List<SpanHandler> spanHandlers,
List<TracingCustomizer> tracingCustomizers, CurrentTraceContext currentTraceContext,
Factory propagationFactory, Sampler sampler) {
if (properties.getBrave().isSpanJoiningSupported()) {
if (properties.getPropagation().getType() != null
&& properties.getPropagation().getType().contains(PropagationType.W3C)) {
throw new IncompatibleConfigurationException("management.tracing.propagation.type",
"management.tracing.brave.span-joining-supported");
}
if (properties.getPropagation().getType() == null
&& properties.getPropagation().getProduce().contains(PropagationType.W3C)) {
throw new IncompatibleConfigurationException("management.tracing.propagation.produce",
"management.tracing.brave.span-joining-supported");
}
if (properties.getPropagation().getType() == null
&& properties.getPropagation().getConsume().contains(PropagationType.W3C)) {
throw new IncompatibleConfigurationException("management.tracing.propagation.consume",
"management.tracing.brave.span-joining-supported");
}
}
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
Builder builder = Tracing.newBuilder()
.currentTraceContext(currentTraceContext)
.traceId128Bit(true)
.supportsJoin(properties.getBrave().isSpanJoiningSupported())
.supportsJoin(false)
.propagationFactory(propagationFactory)
.sampler(sampler)
.localServiceName(applicationName);

@ -138,7 +138,7 @@ class CompositePropagationFactory extends Propagation.Factory {
* @return the B3 propagation factory
*/
private Propagation.Factory b3Single() {
return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE).build();
return B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE_NO_PARENT).build();
}
/**

@ -1,69 +0,0 @@
/*
* 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 Tracing 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;
}
}
}

@ -17,26 +17,17 @@
package org.springframework.boot.actuate.autoconfigure.tracing;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.annotation.DefaultNewSpanParser;
import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor;
import io.micrometer.tracing.annotation.MethodInvocationProcessor;
import io.micrometer.tracing.annotation.NewSpanParser;
import io.micrometer.tracing.annotation.SpanAspect;
import io.micrometer.tracing.annotation.SpanTagAnnotationHandler;
import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler;
import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler;
import io.micrometer.tracing.propagation.Propagator;
import org.aspectj.weaver.Advice;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@ -44,12 +35,11 @@ import org.springframework.core.annotation.Order;
* {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API.
*
* @author Moritz Halbritter
* @author Jonatan Ivanov
* @since 3.0.0
*/
@AutoConfiguration
@ConditionalOnClass(Tracer.class)
@ConditionalOnBean(Tracer.class)
@ConditionalOnEnabledTracing
public class MicrometerTracingAutoConfiguration {
/**
@ -71,6 +61,7 @@ public class MicrometerTracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Tracer.class)
@Order(DEFAULT_TRACING_OBSERVATION_HANDLER_ORDER)
public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer tracer) {
return new DefaultTracingObservationHandler(tracer);
@ -78,7 +69,7 @@ public class MicrometerTracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Propagator.class)
@ConditionalOnBean({ Tracer.class, Propagator.class })
@Order(SENDER_TRACING_OBSERVATION_HANDLER_ORDER)
public PropagatingSenderTracingObservationHandler<?> propagatingSenderTracingObservationHandler(Tracer tracer,
Propagator propagator) {
@ -87,39 +78,11 @@ public class MicrometerTracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Propagator.class)
@ConditionalOnBean({ Tracer.class, Propagator.class })
@Order(RECEIVER_TRACING_OBSERVATION_HANDLER_ORDER)
public PropagatingReceiverTracingObservationHandler<?> propagatingReceiverTracingObservationHandler(Tracer tracer,
Propagator propagator) {
return new PropagatingReceiverTracingObservationHandler<>(tracer, propagator);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class SpanAspectConfiguration {
@Bean
@ConditionalOnMissingBean
DefaultNewSpanParser newSpanParser() {
return new DefaultNewSpanParser();
}
@Bean
@ConditionalOnMissingBean
ImperativeMethodInvocationProcessor imperativeMethodInvocationProcessor(NewSpanParser newSpanParser,
Tracer tracer, ObjectProvider<SpanTagAnnotationHandler> spanTagAnnotationHandler) {
ImperativeMethodInvocationProcessor methodInvocationProcessor = new ImperativeMethodInvocationProcessor(
newSpanParser, tracer);
spanTagAnnotationHandler.ifAvailable(methodInvocationProcessor::setSpanTagAnnotationHandler);
return methodInvocationProcessor;
}
@Bean
@ConditionalOnMissingBean
SpanAspect spanAspect(MethodInvocationProcessor methodInvocationProcessor) {
return new SpanAspect(methodInvocationProcessor);
}
}
}

@ -20,10 +20,6 @@ import java.util.Collections;
import java.util.List;
import io.micrometer.tracing.SpanCustomizer;
import io.micrometer.tracing.exporter.SpanExportingPredicate;
import io.micrometer.tracing.exporter.SpanFilter;
import io.micrometer.tracing.exporter.SpanReporter;
import io.micrometer.tracing.otel.bridge.CompositeSpanExporter;
import io.micrometer.tracing.otel.bridge.EventListener;
import io.micrometer.tracing.otel.bridge.EventPublishingContextWrapper;
import io.micrometer.tracing.otel.bridge.OtelBaggageManager;
@ -36,19 +32,20 @@ import io.micrometer.tracing.otel.bridge.Slf4JBaggageEventListener;
import io.micrometer.tracing.otel.bridge.Slf4JEventListener;
import io.micrometer.tracing.otel.propagation.BaggageTextMapPropagator;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.SpringBootVersion;
@ -60,20 +57,26 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry tracing.
* {@link EnableAutoConfiguration Auto-configuration} for OpenTelemetry.
*
* @author Moritz Halbritter
* @author Marcin Grzejszczak
* @author Yanming Zhou
* @since 3.0.0
*/
@AutoConfiguration(value = "openTelemetryTracingAutoConfiguration", before = MicrometerTracingAutoConfiguration.class)
@AutoConfiguration(before = MicrometerTracingAutoConfiguration.class)
@ConditionalOnEnabledTracing
@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class })
@EnableConfigurationProperties(TracingProperties.class)
public class OpenTelemetryAutoConfiguration {
/**
* Default value for application name if {@code spring.application.name} is not set.
*/
private static final String DEFAULT_APPLICATION_NAME = "application";
private final TracingProperties tracingProperties;
OpenTelemetryAutoConfiguration(TracingProperties tracingProperties) {
@ -82,11 +85,23 @@ public class OpenTelemetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
SdkTracerProvider otelSdkTracerProvider(Resource resource, SpanProcessors spanProcessors, Sampler sampler,
ObjectProvider<SdkTracerProviderBuilderCustomizer> customizers) {
SdkTracerProviderBuilder builder = SdkTracerProvider.builder().setSampler(sampler).setResource(resource);
spanProcessors.forEach(builder::addSpanProcessor);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
OpenTelemetry openTelemetry(SdkTracerProvider sdkTracerProvider, ContextPropagators contextPropagators) {
return OpenTelemetrySdk.builder()
.setTracerProvider(sdkTracerProvider)
.setPropagators(contextPropagators)
.build();
}
@Bean
@ConditionalOnMissingBean
SdkTracerProvider otelSdkTracerProvider(Environment environment, ObjectProvider<SpanProcessor> spanProcessors,
Sampler sampler) {
String applicationName = environment.getProperty("spring.application.name", DEFAULT_APPLICATION_NAME);
Resource springResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName));
SdkTracerProviderBuilder builder = SdkTracerProvider.builder()
.setSampler(sampler)
.setResource(Resource.getDefault().merge(springResource));
spanProcessors.orderedStream().forEach(builder::addSpanProcessor);
return builder.build();
}
@ -104,26 +119,12 @@ public class OpenTelemetryAutoConfiguration {
}
@Bean
@ConditionalOnMissingBean
SpanProcessors spanProcessors(ObjectProvider<SpanProcessor> spanProcessors) {
return SpanProcessors.of(spanProcessors.orderedStream().toList());
SpanProcessor otelSpanProcessor(ObjectProvider<SpanExporter> spanExporters) {
return SpanProcessor.composite(spanExporters.orderedStream().map(this::buildBatchSpanProcessor).toList());
}
@Bean
BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters,
ObjectProvider<SpanExportingPredicate> spanExportingPredicates, ObjectProvider<SpanReporter> spanReporters,
ObjectProvider<SpanFilter> spanFilters, ObjectProvider<MeterProvider> meterProvider) {
BatchSpanProcessorBuilder builder = BatchSpanProcessor
.builder(new CompositeSpanExporter(spanExporters.list(), spanExportingPredicates.orderedStream().toList(),
spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList()));
meterProvider.ifAvailable(builder::setMeterProvider);
return builder.build();
}
@Bean
@ConditionalOnMissingBean
SpanExporters spanExporters(ObjectProvider<SpanExporter> spanExporters) {
return SpanExporters.of(spanExporters.orderedStream().toList());
private SpanProcessor buildBatchSpanProcessor(SpanExporter exporter) {
return BatchSpanProcessor.builder(exporter).build();
}
@Bean

@ -1,38 +0,0 @@
/*
* 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 io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
/**
* Callback interface that can be used to customize the {@link SdkTracerProviderBuilder}
* that is used to create the auto-configured {@link SdkTracerProvider}.
*
* @author Yanming Zhou
* @since 3.1.0
*/
@FunctionalInterface
public interface SdkTracerProviderBuilderCustomizer {
/**
* Customize the given {@code builder}.
* @param builder the builder to customize
*/
void customize(SdkTracerProviderBuilder builder);
}

@ -1,76 +0,0 @@
/*
* 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 java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import org.springframework.util.Assert;
/**
* A collection of {@link SpanExporter span exporters}.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@FunctionalInterface
public interface SpanExporters extends Iterable<SpanExporter> {
/**
* Returns the list of {@link SpanExporter span exporters}.
* @return the list of span exporters
*/
List<SpanExporter> list();
@Override
default Iterator<SpanExporter> iterator() {
return list().iterator();
}
@Override
default Spliterator<SpanExporter> spliterator() {
return list().spliterator();
}
/**
* Constructs a {@link SpanExporters} instance with the given {@link SpanExporter span
* exporters}.
* @param spanExporters the span exporters
* @return the constructed {@link SpanExporters} instance
*/
static SpanExporters of(SpanExporter... spanExporters) {
return of(Arrays.asList(spanExporters));
}
/**
* Constructs a {@link SpanExporters} instance with the given list of
* {@link SpanExporter span exporters}.
* @param spanExporters the list of span exporters
* @return the constructed {@link SpanExporters} instance
*/
static SpanExporters of(Collection<? extends SpanExporter> spanExporters) {
Assert.notNull(spanExporters, "SpanExporters must not be null");
List<SpanExporter> copy = List.copyOf(spanExporters);
return () -> copy;
}
}

@ -1,76 +0,0 @@
/*
* 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 java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import io.opentelemetry.sdk.trace.SpanProcessor;
import org.springframework.util.Assert;
/**
* A collection of {@link SpanProcessor span processors}.
*
* @author Moritz Halbritter
* @since 3.2.0
*/
@FunctionalInterface
public interface SpanProcessors extends Iterable<SpanProcessor> {
/**
* Returns the list of {@link SpanProcessor span processors}.
* @return the list of span processors
*/
List<SpanProcessor> list();
@Override
default Iterator<SpanProcessor> iterator() {
return list().iterator();
}
@Override
default Spliterator<SpanProcessor> spliterator() {
return list().spliterator();
}
/**
* Constructs a {@link SpanProcessors} instance with the given {@link SpanProcessor
* span processors}.
* @param spanProcessors the span processors
* @return the constructed {@link SpanProcessors} instance
*/
static SpanProcessors of(SpanProcessor... spanProcessors) {
return of(Arrays.asList(spanProcessors));
}
/**
* Constructs a {@link SpanProcessors} instance with the given list of
* {@link SpanProcessor span processors}.
* @param spanProcessors the list of span processors
* @return the constructed {@link SpanProcessors} instance
*/
static SpanProcessors of(Collection<? extends SpanProcessor> spanProcessors) {
Assert.notNull(spanProcessors, "SpanProcessors must not be null");
List<SpanProcessor> copy = List.copyOf(spanProcessors);
return () -> copy;
}
}

@ -25,7 +25,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* Configuration properties for tracing.
*
* @author Moritz Halbritter
* @author Jonatan Ivanov
* @since 3.0.0
*/
@ConfigurationProperties("management.tracing")
@ -51,11 +50,6 @@ public class TracingProperties {
*/
private final Propagation propagation = new Propagation();
/**
* Brave configuration.
*/
private final Brave brave = new Brave();
public boolean isEnabled() {
return this.enabled;
}
@ -76,10 +70,6 @@ public class TracingProperties {
return this.propagation;
}
public Brave getBrave() {
return this.brave;
}
public static class Sampling {
/**
@ -260,23 +250,4 @@ public class TracingProperties {
}
public static class Brave {
/**
* Whether the propagation type and tracing backend support sharing the span ID
* between client and server spans. Requires B3 propagation and a compatible
* backend.
*/
private boolean spanJoiningSupported = false;
public boolean isSpanJoiningSupported() {
return this.spanJoiningSupported;
}
public void setSpanJoiningSupported(boolean spanJoiningSupported) {
this.spanJoiningSupported = spanJoiningSupported;
}
}
}

@ -1,53 +0,0 @@
/*
* 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.otlp;
import io.micrometer.tracing.otel.bridge.OtelTracer;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for OTLP. Brave does not support
* OTLP, so we only configure it for OpenTelemetry. OTLP defines three transports that are
* supported: gRPC (/protobuf), HTTP/protobuf, HTTP/JSON. From these transports HTTP/JSON
* is not supported by the OTel Java SDK, and it seems there are no plans supporting it in
* the future, see: <a href=
* "https://github.com/open-telemetry/opentelemetry-java/issues/3651">opentelemetry-java#3651</a>.
* Because this class configures components from the OTel SDK, it can't support HTTP/JSON.
* To keep things simple, we only auto-configure HTTP/protobuf. If you want to use gRPC,
* define an {@link OtlpGrpcSpanExporter} and this auto-configuration will back off.
*
* @author Jonatan Ivanov
* @author Moritz Halbritter
* @author Eddú Meléndez
* @since 3.1.0
*/
@AutoConfiguration
@ConditionalOnClass({ OtelTracer.class, SdkTracerProvider.class, OpenTelemetry.class, OtlpHttpSpanExporter.class })
@EnableConfigurationProperties(OtlpProperties.class)
@Import({ OtlpTracingConfigurations.ConnectionDetails.class, OtlpTracingConfigurations.Exporters.class })
public class OtlpAutoConfiguration {
}

@ -1,103 +0,0 @@
/*
* 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.otlp;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for exporting traces using OTLP.
*
* @author Jonatan Ivanov
* @since 3.1.0
*/
@ConfigurationProperties("management.otlp.tracing")
public class OtlpProperties {
/**
* URL to the OTel collector's HTTP API.
*/
private String endpoint;
/**
* Call timeout for the OTel Collector to process an exported batch of data. This
* timeout spans the entire call: resolving DNS, connecting, writing the request body,
* server processing, and reading the response body. If the call requires redirects or
* retries all must complete within one timeout period.
*/
private Duration timeout = Duration.ofSeconds(10);
/**
* Method used to compress the payload.
*/
private Compression compression = Compression.NONE;
/**
* Custom HTTP headers you want to pass to the collector, for example auth headers.
*/
private Map<String, String> headers = new HashMap<>();
public String getEndpoint() {
return this.endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public Duration getTimeout() {
return this.timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
public Compression getCompression() {
return this.compression;
}
public void setCompression(Compression compression) {
this.compression = compression;
}
public Map<String, String> getHeaders() {
return this.headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
enum Compression {
/**
* Gzip compression.
*/
GZIP,
/**
* No compression.
*/
NONE
}
}

@ -1,93 +0,0 @@
/*
* 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.otlp;
import java.util.Map.Entry;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configurations imported by {@link OtlpAutoConfiguration}.
*
* @author Moritz Halbritter
*/
final class OtlpTracingConfigurations {
private OtlpTracingConfigurations() {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetails {
@Bean
@ConditionalOnMissingBean(OtlpTracingConnectionDetails.class)
@ConditionalOnProperty(prefix = "management.otlp.tracing", name = "endpoint")
OtlpTracingConnectionDetails otlpTracingConnectionDetails(OtlpProperties properties) {
return new PropertiesOtlpTracingConnectionDetails(properties);
}
/**
* Adapts {@link OtlpProperties} to {@link OtlpTracingConnectionDetails}.
*/
static class PropertiesOtlpTracingConnectionDetails implements OtlpTracingConnectionDetails {
private final OtlpProperties properties;
PropertiesOtlpTracingConnectionDetails(OtlpProperties properties) {
this.properties = properties;
}
@Override
public String getUrl() {
return this.properties.getEndpoint();
}
}
}
@Configuration(proxyBeanMethods = false)
static class Exporters {
@Bean
@ConditionalOnMissingBean(value = OtlpHttpSpanExporter.class,
type = "io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter")
@ConditionalOnBean(OtlpTracingConnectionDetails.class)
@ConditionalOnEnabledTracing
OtlpHttpSpanExporter otlpHttpSpanExporter(OtlpProperties properties,
OtlpTracingConnectionDetails connectionDetails) {
OtlpHttpSpanExporterBuilder builder = OtlpHttpSpanExporter.builder()
.setEndpoint(connectionDetails.getUrl())
.setTimeout(properties.getTimeout())
.setCompression(properties.getCompression().name().toLowerCase());
for (Entry<String, String> header : properties.getHeaders().entrySet()) {
builder.addHeader(header.getKey(), header.getValue());
}
return builder.build();
}
}
}

@ -1,35 +0,0 @@
/*
* 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.otlp;
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
/**
* Details required to establish a connection to a OpenTelemetry service.
*
* @author Eddú Meléndez
* @since 3.2.0
*/
public interface OtlpTracingConnectionDetails extends ConnectionDetails {
/**
* Address to where metrics will be published.
* @return the address to where metrics will be published
*/
String getUrl();
}

@ -1,20 +0,0 @@
/*
* 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.
*/
/**
* Auto-configuration for tracing with OTLP.
*/
package org.springframework.boot.actuate.autoconfigure.tracing.otlp;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -22,6 +22,7 @@ import io.prometheus.client.exemplars.tracer.common.SpanContextSupplier;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
import org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -42,6 +43,7 @@ import org.springframework.util.function.SingletonSupplier;
after = MicrometerTracingAutoConfiguration.class)
@ConditionalOnBean(Tracer.class)
@ConditionalOnClass({ Tracer.class, SpanContextSupplier.class })
@ConditionalOnEnabledTracing
public class PrometheusExemplarsAutoConfiguration {
@Bean

@ -52,6 +52,7 @@ import org.springframework.context.annotation.Import;
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
WavefrontAutoConfiguration.class })
@ConditionalOnClass({ WavefrontSender.class, WavefrontSpanHandler.class })
@ConditionalOnEnabledTracing
@EnableConfigurationProperties(WavefrontProperties.class)
@Import(WavefrontSenderConfiguration.class)
public class WavefrontTracingAutoConfiguration {
@ -59,7 +60,6 @@ public class WavefrontTracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(WavefrontSender.class)
@ConditionalOnEnabledTracing
WavefrontSpanHandler wavefrontSpanHandler(WavefrontProperties properties, WavefrontSender wavefrontSender,
SpanMetrics spanMetrics, ApplicationTags applicationTags) {
return new WavefrontSpanHandler(properties.getSender().getMaxQueueSize(), wavefrontSender, spanMetrics,
@ -96,7 +96,6 @@ public class WavefrontTracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledTracing
WavefrontBraveSpanHandler wavefrontBraveSpanHandler(WavefrontSpanHandler wavefrontSpanHandler) {
return new WavefrontBraveSpanHandler(wavefrontSpanHandler);
}
@ -109,7 +108,6 @@ public class WavefrontTracingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledTracing
WavefrontOtelSpanExporter wavefrontOtelSpanExporter(WavefrontSpanHandler wavefrontSpanHandler) {
return new WavefrontOtelSpanExporter(wavefrontSpanHandler);
}

@ -1,39 +0,0 @@
/*
* 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.zipkin;
/**
* Adapts {@link ZipkinProperties} to {@link ZipkinConnectionDetails}.
*
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
*/
class PropertiesZipkinConnectionDetails implements ZipkinConnectionDetails {
private final ZipkinProperties properties;
PropertiesZipkinConnectionDetails(ZipkinProperties properties) {
this.properties = properties;
}
@Override
public String getSpanEndpoint() {
return this.properties.getEndpoint();
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2022 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.
@ -21,6 +21,7 @@ import zipkin2.codec.BytesEncoder;
import zipkin2.codec.SpanBytesEncoder;
import zipkin2.reporter.Sender;
import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.BraveConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.OpenTelemetryConfiguration;
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinConfigurations.ReporterConfiguration;
@ -30,13 +31,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Zipkin.
* <p>
*
* It uses imports on {@link ZipkinConfigurations} to guarantee the correct configuration
* ordering.
*
@ -47,15 +47,9 @@ import org.springframework.context.annotation.Import;
@ConditionalOnClass(Sender.class)
@Import({ SenderConfiguration.class, ReporterConfiguration.class, BraveConfiguration.class,
OpenTelemetryConfiguration.class })
@EnableConfigurationProperties(ZipkinProperties.class)
@ConditionalOnEnabledTracing
public class ZipkinAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ZipkinConnectionDetails.class)
PropertiesZipkinConnectionDetails zipkinConnectionDetails(ZipkinProperties properties) {
return new PropertiesZipkinConnectionDetails(properties);
}
@Bean
@ConditionalOnMissingBean
public BytesEncoder<Span> spanBytesEncoder() {

@ -26,7 +26,6 @@ import zipkin2.reporter.brave.ZipkinSpanHandler;
import zipkin2.reporter.urlconnection.URLConnectionSender;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -60,14 +59,11 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
URLConnectionSender urlConnectionSender(ZipkinProperties properties,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
URLConnectionSender urlConnectionSender(ZipkinProperties properties) {
URLConnectionSender.Builder builder = URLConnectionSender.newBuilder();
builder.connectTimeout((int) properties.getConnectTimeout().toMillis());
builder.readTimeout((int) properties.getReadTimeout().toMillis());
builder.endpoint(connectionDetails.getSpanEndpoint());
builder.endpoint(properties.getEndpoint());
return builder.build();
}
@ -81,15 +77,12 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
ZipkinRestTemplateSender restTemplateSender(ZipkinProperties properties,
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
ObjectProvider<ZipkinRestTemplateBuilderCustomizer> customizers) {
RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder()
.setConnectTimeout(properties.getConnectTimeout())
.setReadTimeout(properties.getReadTimeout());
restTemplateBuilder = applyCustomizers(restTemplateBuilder, customizers);
return new ZipkinRestTemplateSender(connectionDetails.getSpanEndpoint(), restTemplateBuilder.build());
return new ZipkinRestTemplateSender(properties.getEndpoint(), restTemplateBuilder.build());
}
private RestTemplateBuilder applyCustomizers(RestTemplateBuilder restTemplateBuilder,
@ -113,14 +106,10 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean(Sender.class)
ZipkinWebClientSender webClientSender(ZipkinProperties properties,
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers,
ObjectProvider<ZipkinConnectionDetails> connectionDetailsProvider) {
ZipkinConnectionDetails connectionDetails = connectionDetailsProvider
.getIfAvailable(() -> new PropertiesZipkinConnectionDetails(properties));
ObjectProvider<ZipkinWebClientBuilderCustomizer> customizers) {
WebClient.Builder builder = WebClient.builder();
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return new ZipkinWebClientSender(connectionDetails.getSpanEndpoint(), builder.build(),
properties.getConnectTimeout().plus(properties.getReadTimeout()));
return new ZipkinWebClientSender(properties.getEndpoint(), builder.build());
}
}
@ -144,7 +133,6 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Reporter.class)
@ConditionalOnEnabledTracing
ZipkinSpanHandler zipkinSpanHandler(Reporter<Span> spanReporter) {
return (ZipkinSpanHandler) ZipkinSpanHandler.newBuilder(spanReporter).build();
}
@ -158,7 +146,6 @@ class ZipkinConfigurations {
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(Sender.class)
@ConditionalOnEnabledTracing
ZipkinSpanExporter zipkinSpanExporter(BytesEncoder<Span> encoder, Sender sender) {
return ZipkinSpanExporter.builder().setEncoder(encoder).setSender(sender).build();
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save