Skip to content

15.2.2 Viewing configuration details

Beyond receiving general information about an application, it can be enlightening to understand how an application is configured. What beans are in the application context? What autoconfiguration conditions passed or failed? What environment properties are available to the application? How are HTTP requests mapped to controllers? What logging level are one or more packages or classes set to?

These questions are answered by Actuator’s /beans, /conditions, /env, /configprops, /mappings, and /loggers endpoints. And in some cases, such as /env and /loggers, you can even adjust the configuration of a running application on the fly. We’ll look at how each of these endpoints gives insight into the configuration of a running application, starting with the /beans endpoint.

GETTING A BEAN WIRING REPORT

The most essential endpoint for exploring the Spring application context is the /beans endpoint. This endpoint returns a JSON document describing every single bean in the application context, its Java type, and any of the other beans it’s injected with.

A complete response from a GET request to /beans could easily fill this entire chapter. Instead of examining the complete response from /beans, let’s consider the following snippet, which focuses on a single bean entry:

text
{
    "contexts": {
        "application-1": {
            "beans": {
   ...
                "ingredientsController": {
                    "aliases": [],
                    "scope": "singleton",
                    "type": "tacos.ingredients.IngredientsController",
                    "resource": "file [/Users/habuma/Documents/Workspaces/
                    TacoCloud/ingredient-service/target/classes/tacos/ingredients/
                    IngredientsController.class]",
                    "dependencies": [
                        "ingredientRepository"
                    ]
                },
        ...
            },
            "parentId": null
        }
    }
}

At the root of the response is the contexts element, which includes one subelement for each Spring application context in the application. Within each application context is a beans element that holds details for all the beans in the application context.

In the preceding example, the bean shown is the one whose name is ingredientsController. You can see that it has no aliases, is scoped as a singleton, and is of type tacos.ingredients.IngredientsController. Moreover, the resource property gives the path to the class file that defines the bean. And the dependencies property lists all other beans that are injected into the given bean. In this case, the ingredientsController bean is injected with a bean whose name is ingredientRepository.

EXPLAINING AUTOCONFIGURATION

As you’ve seen, autoconfiguration is one of the most powerful things that Spring Boot offers. Sometimes, however, you may wonder why something has been autoconfigured. Or you may expect something to have been autoconfigured and are left wondering why it hasn’t been. In that case, you can make a GET request to /conditions to get an explanation of what took place in autoconfiguration.

The autoconfiguration report returned from /conditions is divided into three parts: positive matches (conditional configuration that passed), negative matches (conditional configuration that failed), and unconditional classes. The following snippet from the response to a request to /conditions shows an example of each section:

json
{
  "contexts": {
    "application-1": {
      "positiveMatches": {
      ...
        "MongoDataAutoConfiguration#mongoTemplate": [
          {
            "condition": "OnBeanCondition",
            "message": "@ConditionalOnMissingBean (types
            :org.springframework.data.mongodb.core.MongoTemplate;
            SearchStrategy: all) did not find any beans"
          }
        ],
      ...
    },
    "negativeMatches": {
      ...
      "DispatcherServletAutoConfiguration": {
        "notMatched": [
          {
            "condition": "OnClassCondition",
            "message": "@ConditionalOnClass did not find required
            class 'org.springframework.web.servlet.
            DispatcherServlet'"
          }
        ],
        "matched": []
      },
      ...
    },
    "unconditionalClasses": [
      ...
      "org.springframework.boot.autoconfigure.context.
      ConfigurationPropertiesAutoConfiguration",
      ...
    ]
  }
  }
}

Under the positiveMatches section, you see that a MongoTemplate bean was configured by autoconfiguration because one didn’t already exist. The autoconfiguration that caused this includes a @ConditionalOnMissingBean annotation, which passes off the bean to be configured if it hasn’t already been explicitly configured. But in this case, no beans of type MongoTemplate were found, so autoconfiguration stepped in and configured one.

Under negativeMatches, Spring Boot autoconfiguration considered configuring a DispatcherServlet. But the @ConditionalOnClass conditional annotation failed because DispatcherServlet couldn’t be found.

Finally, a ConfigurationPropertiesAutoConfiguration bean was configured unconditionally, as seen under the unconditionalClasses section. Configuration properties are foundational to how Spring Boot operates, so you should autoconfigure any configuration pertaining to configuration properties without any conditions.

INSPECTING THE ENVIRONMENT AND CONFIGURATION PROPERTIES

In addition to knowing how your application beans are wired together, you might also be interested in learning what environment properties are available and what configuration properties were injected into the beans.

When you issue a GET request to the /env endpoint, you’ll receive a rather lengthy response that includes properties from all property sources in play in the Spring application. This includes properties from environment variables, JVM system properties, application.properties and application.yml files, and even the Spring Cloud Config Server (if the application is a client of the Config Server).

The following listing shows a greatly abridged example of the kind of response you might get from the /env endpoint, to give you some idea of the kind of information it provides.

Listing 15.1 The results from the /env endpoint

bash
$ curl localhost:8081/actuator/env
{
  "activeProfiles": [
    "development"
  ],
  "propertySources": [
  ...
  {
    "name": "systemEnvironment",
    "properties": {
      "PATH": {
        "value": "/usr/bin:/bin:/usr/sbin:/sbin",
        "origin": "System Environment Property \"PATH\""
      },
      ...
      "HOME": {
        "value": "/Users/habuma",
        "origin": "System Environment Property \"HOME\""
      }
    }
  },
  {
    "name": "applicationConfig: [classpath:/application.yml]",
    "properties": {
      "spring.application.name": {
        "value": "ingredient-service",
        "origin": "class path resource [application.yml]:3:11"
      },
      "server.port": {
        "value": 8081,
        "origin": "class path resource [application.yml]:9:9"
      },
      ...
    }
  },
  ...
  ]
}

Although the full response from /env provides even more information, what’s shown in listing 15.1 contains a few noteworthy elements. First, notice that near the top of the response is a field named activeProfiles. In this case, it indicates that the development profile is active. If any other profiles were active, those would be listed as well.

Next, the propertySources field is an array containing an entry for every property source in the Spring application environment. In listing 15.1, only the systemEnvironment and an applicationConfig property source referencing the application.yml file are shown.

Within each property source is a listing of all properties provided by that source, paired with their values. In the case of the application.yml property source, the origin field for each property tells exactly where the property is set, including the line and column within application.yml.

The /env endpoint can also be used to fetch a specific property when that property’s name is given as the second element of the path. For example, to examine the server.port property, submit a GET request for /env/server.port, as shown here:

bash
$ curl localhost:8081/actuator/env/server.port
{
  "property": {
    "source": "systemEnvironment", "value": "8081"
  },
  "activeProfiles": [ "development" ],
  "propertySources": [
    { "name": "server.ports" },
    { "name": "mongo.ports" },
    { "name": "systemProperties" },
    { 
      "name": "systemEnvironment",
      "property": {
        "value": "8081",
        "origin": "System Environment Property \"SERVER_PORT\""
      }
    },
    { "name": "random" },
    {
      "name": "applicationConfig: [classpath:/application.yml]",
      "property": {
        "value": 0,
        "origin": "class path resource [application.yml]:9:9"
      }
    },
    { "name": "springCloudClientHostInfo" },
    { "name": "refresh" },
    { "name": "defaultProperties" },
    { "name": "Management Server" }
  ]
}

As you can see, all property sources are still represented, but only those that set the specified property will contain any additional information. In this case, both the systemEnvironment property source and the application.yml property source had values for the server.port property. Because the systemEnvironment property source takes precedence over any of the property sources listed below it, its value of 8080 wins. The winning value is reflected near the top under the property field.

The /env endpoint can be used for more than just reading property values. By submitting a POST request to the /env endpoint, along with a JSON document with name and value fields, you can also set properties in the running application. For example, to set a property named tacocloud.discount.code to TACOS1234, you can use curl to submit the POST request at the command line like this:

bash
$ curl localhost:8081/actuator/env \
  -d'{"name":"tacocloud.discount.code","value":"TACOS1234"}' \
  -H "Content-type: application/json"
{"tacocloud.discount.code":"TACOS1234"}

After submitting the property, the newly set property and its value are returned in the response. Later, should you decide you no longer need that property, you can submit a DELETE request to the /env endpoint as follows to delete all properties created through that endpoint:

bash
$ curl localhost:8081/actuator/env -X DELETE
{"tacocloud.discount.code":"TACOS1234"}

As useful as setting properties through Actuator’s API can be, it’s important to be aware that any properties set with a POST request to the /env endpoint apply only to the application instance receiving the request, are temporary, and will be lost when the application restarts.

Although Spring MVC’s (and Spring WebFlux’s) programming model makes it easy to handle HTTP requests by simply annotating methods with request-mapping annotations, it can sometimes be challenging to get a big-picture understanding of all the kinds of HTTP requests that an application can handle and what kinds of components handle those requests.

Actuator’s /mappings endpoint offers a one-stop view of every HTTP request handler in an application, whether it be from a Spring MVC controller or one of Actuator’s own endpoints. To get a complete list of all the endpoints in a Spring Boot application, make a GET request to the /mappings endpoint, and you might receive something that’s a little bit like the abridged response shown next.

Listing 15.2 HTTP mappings as shown by the /mappings endpoint

bash
$ curl localhost:8081/actuator/mappings | jq
{
  "contexts": {
    "application-1": {
      "mappings": {
        "dispatcherHandlers": {
          "webHandler": [
            ...
            {
              "predicate": "{[/ingredients],methods=[GET]}",
              "handler": "public
              reactor.core.publisher.Flux<tacos.ingredients.Ingredient>
              tacos.ingredients.IngredientsController.allIngredients()",
              "details": {
                "handlerMethod": {
                  "className": "tacos.ingredients.IngredientsController",
                  "name": "allIngredients",
                  "descriptor": "()Lreactor/core/publisher/Flux;"
                },
                "handlerFunction": null,
                "requestMappingConditions": {
                  "consumes": [],
                  "headers": [],
                  "methods": [
                    "GET"
                  ],
                  "params": [],
                  "patterns": [
                    "/ingredients"
                  ],
                  "produces": []
                }
              }
            },
            ...
            ]
          }
        },
        "parentId": "application-1"
      },
      "bootstrap": {
        "mappings": {
          "dispatcherHandlers": {}
        },
        "parentId": null
      }
    }
}

Here, the response from the curl command line is piped to a utility called jq https://stedolan.github.io/jq/, which, among other things, pretty-prints the JSON returned from the request in an easily readable format. For the sake of brevity, this response has been abridged to show only a single request handler. Specifically, it shows that GET requests for /ingredients will be handled by the allIngredients() method of IngredientsController.

MANAGING LOGGING LEVELS

Logging is an important feature of any application. Logging can provide a means of auditing as well as a crude means of debugging.

Setting logging levels can be quite a balancing act. If you set the logging level to be too verbose, there may be too much noise in the logs, and finding useful information may be difficult. On the other hand, if you set logging levels to be too slack, the logs may not be of much value in understanding what an application is doing.

Logging levels are typically applied on a package-by-package basis. If you’re ever wondering what logging levels are set in your running Spring Boot application, you can issue a GET request to the /loggers endpoint. The following JSON code shows an excerpt from a response to /loggers:

bash
{
  "levels": [ "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" ],
  "loggers": {
    "ROOT": {
      "configuredLevel": "INFO", "effectiveLevel": "INFO"
    },
    ...
    "org.springframework.web": {
      "configuredLevel": null, "effectiveLevel": "INFO"
    },
    ...
    "tacos": {
      "configuredLevel": null, "effectiveLevel": "INFO"
    },
    "tacos.ingredients": {
      "configuredLevel": null, "effectiveLevel": "INFO"
    },
    "tacos.ingredients.IngredientServiceApplication": {
      "configuredLevel": null, "effectiveLevel": "INFO"
    }
  }
}

The response starts off with a list of all valid logging levels. After that, the loggers element lists logging-level details for each package in the application. The configuredLevel property shows the logging level that has been explicitly configured (or null if it hasn’t been explicitly configured). The effectiveLevel property gives the effective logging level, which may have been inherited from a parent package or from the root logger.

Although this excerpt shows logging levels only for the root logger and four packages, the complete response will include logging-level entries for every single package in the application, including those for libraries that are in use. If you’d rather focus your request on a specific package, you can specify the package name as an extra path component in the request.

For example, if you just want to know what logging levels are set for the tacocloud.ingredients package, you can make a request to /loggers/tacos.ingredients as follows:

bash
{
  "configuredLevel": null,
  "effectiveLevel": "INFO"
}

Aside from returning the logging levels for the application packages, the /loggers endpoint also allows you to change the configured logging level by issuing a POST request. For example, suppose you want to set the logging level of the tacocloud.ingredients package to DEBUG. The following curl command will achieve that:

bash
$ curl localhost:8081/actuator/loggers/tacos/ingredients \
  -d'{"configuredLevel":"DEBUG"}' \
  -H"Content-type: application/json"

Now that the logging level has been changed, you can issue a GET request to /loggers/tacos/ingredients as shown here to see that it has been changed:

bash
{
  "configuredLevel": "DEBUG",
  "effectiveLevel": "DEBUG"
}

Notice that where the configuredLevel was previously null, it’s now DEBUG. That change carries over to the effectiveLevel as well. But what’s most important is that if any code in that package logs anything at debug level, the log files will include that debug-level information.

Released under the MIT License.