While the Actuator provides many details of the inner workings of Spring Boot apps in operation, it doesn’t necessarily fit your needs. You may not need all the features it offers, and you may want to turn some off. Alternatively, you need to expand the Actuator a bit, adding some custom metrics to fit your application requirements.

In fact, the Robot can be customized in several ways, including the following five.

  • Rename the endpoint.
  • Enable and disable endpoints.
  • Customize measurement information.
  • Create a custom repository to store trace data.
  • Insert custom health indicators.

Next, we learn how to customize the Actuator to meet our needs. Let’s start with one of the simplest customizations: rename the endpoints of the Actuator.

Change the endpoint ID

Each Actuator endpoint has an ID that determines the endpoint path. For example, the default ID of a /beans endpoint is beans.

If an endpoint’s path is determined by its ID, you can change the endpoint’s path by changing the ID. All you have to do is set a property called endpoints.endpoint-id.id.

As a demonstration, we use the /shutdown endpoint, which responds to POST requests to /shutdown. If you want it to handle POST requests to /kill, you can use YAML to give /shutdown a new ID, that is, a new path:

endpoints: 
 shutdown:
  id: kill
Copy the code

There are many reasons to rename endpoints and change their paths. The most obvious reason is to name the endpoints in line with the team’s terminology. You may also want to rename the endpoint to make it invisible to those familiar with the default name for added security.

Unfortunately, renaming endpoints doesn’t really protect you, just make it slower for hackers to find them. Now let’s take a look at disabling one (or all) of the endpoints that you don’t want others to access.

Enable and disable endpoints

While the endpoints of the Actuator are all useful, you don’t need all of them. By default, all endpoints except /shutdown are enabled. Here is not described in detail how to set the endpoints, shutdown, enabled to true, to open/shutdown endpoint. In the same way, you can disable other endpoints by setting endpoint.endpoint-id. enabled to false.

For example, to disable/metrics endpoints, you have to do is to endpoints. The metrics, enabled attribute is set to false. Make the following Settings in application.yml:

endpoints: 
 metrics: 
 enabled: false
Copy the code

If you only want to open one or two endpoints, it’s easier to disable all endpoints and then enable the ones you want. For example, consider the following application.yML fragment:

endpoints: 
 enabled: false 
 metrics: 
 enabled: true
Copy the code

As shown in the above snippet, endpoints. Enabled is set to false to disable all physical endpoint, then endpoints. The metrics. The enabled is set to true to enable/metrics endpoint.

3. Add custom metrics

You may also want to define your own metrics to capture specific information in your application.

For example, if we want to know how many times a user has saved a book to the reading list, the easiest way to do this is to increment the counter each time we call the ReadingListController addToReadingList() method. Counters are easy to implement, but how does this constantly changing total value come together with metrics published by the /metrics endpoint?

Or suppose we want to get the timestamp of the last book saved. The timestamp can be obtained by calling System.currentTime-millis (), but how do you report this timestamp in the /metrics endpoint?

In fact, autoconfiguration allows the Actuator to create instances of CounterService and register them as beans in Spring’s application context. The CounterService interface defines three methods for adding, subtracting, or resetting measures for a given name, as follows:

package org.springframework.boot.actuate.metrics; 
public interface CounterService { 
 void increment(String metricName); 
 void decrement(String metricName); 
 void reset(String metricName); 
}
Copy the code

The automatic configuration of the Actuator also configures a GaugeService type Bean. This interface is similar to CounterService in that it can record a value to a measure with a specific name. GaugeService looks like this:

package org.springframework.boot.actuate.metrics; 
public interface GaugeService { 
 void submit(String metricName, double value); 
}
Copy the code

You don’t have to implement these interfaces. Spring Boot already provides implementations of both. All we have to do is inject their instances into the desired beans, call the methods in them when appropriate, and update the desired metrics.

For the requirements mentioned above, inject CounterService and GaugeService beans into the Reading-ListController and call the methods in the addToReadingList() method.

Use injected CounterService and GaugeService

    @Controller 
    @RequestMapping("/") 
    @ConfigurationProperties("amazon") 
    public class ReadingListController {...private CounterService counterService; 
     @Autowired 
     public ReadingListController( ReadingListRepository readingListRepository, AmazonProperties amazonProperties, CounterService counterService, GaugeService gaugeService) { 
     this.readingListRepository = readingListRepository; 
     this.amazonProperties = amazonProperties; 
     this.counterService = counterService; 
     this.gaugeService = gaugeService; }...@RequestMapping(method=RequestMethod.POST) 
     public String addToReadingList(Reader reader, Book book) { 
     book.setReader(reader); 
     readingListRepository.save(book); 
     counterService.increment("books.saved");
     gaugeService.submit( 
     "books.last.saved", System.currentTimeMillis()); 
     return "redirect:/"; }}Copy the code

The modified ReadingListController uses an auto-weaving mechanism to inject CounterService and GaugeService through the controller constructor and then store them in instance variables. Since then, The addtoReading-list () method calls Counterservice.increment (“books.saved”) and Gaugeservice.submit (” books.last.saved “) to adjust the measurement each time a request is processed.

Although CounterService and GaugeService are simple to use, there are some measurements that are difficult to capture by adding counters or recording indicator values. In those cases, we can implement the PublicMetrics interface to provide the metrics we need. This interface defines a metrics() method that returns a set of metrics objects:

package org.springframework.boot.actuate.endpoint; public interface PublicMetrics { Collection<Metric<? >> metrics(); }Copy the code

To see how PublicMetrics are used, suppose we wanted to report some metrics from the Spring application context — the time the application context started, the beans, and the number of Bean definitions — that would be interesting to include. By the way, report the number of beans annotated @Controller.

Publish custom measurement information

    package readinglist; 
    import java.util.ArrayList; 
    import java.util.Collection; 
    import java.util.List; 
    import org.springframework.beans.factory.annotation.Autowired; 
    import org.springframework.boot.actuate.endpoint.PublicMetrics; 
    import org.springframework.boot.actuate.metrics.Metric; 
    import org.springframework.context.ApplicationContext; 
    import org.springframework.stereotype.Component; 
    import org.springframework.stereotype.Controller; 
    @Component 
    public class ApplicationContextMetrics implements PublicMetrics { 
     private ApplicationContext context;
     @Autowired 
     public ApplicationContextMetrics(ApplicationContext context) { 
     this.context = context; 
     } 
     @Override 
     publicCollection<Metric<? >> metrics() { List<Metric<? >> metrics =newArrayList<Metric<? > > (); metrics.add(new Metric<Long>("spring.context.startup-date",
     context.getStartupDate())); 
     metrics.add(new Metric<Integer>("spring.beans.definitions",
     context.getBeanDefinitionCount())); 
     metrics.add(new Metric<Integer>("spring.beans", 
     context.getBeanNamesForType(Object.class).length));
     metrics.add(new Metric<Integer>("spring.controllers", 
     context.getBeanNamesForAnnotation(Controller.class).length));
     returnmetrics; }}Copy the code

Physical invokes the metrics () method, collecting ApplicationContextMetrics measurement information. This method calls the method on the injected ApplicationContext to get the amount we want to report as a measure. For each measure, an instance of Metrics is created, specifying the name and value of the measure and adding it to the list to be returned. Create ApplicationContextMetrics and use in ReadingListController Counter – Service and GaugeService, we can find the following entries in/metrics endpoint response:

{... spring.context.startup-date: 1429398980443, spring.beans.definitions: 261, spring.beans: 272, spring.controllers: 2, books.count: 1, gauge.books.save.time: 1429399793260, ... }Copy the code

Of course, the actual value of these measures will vary depending on how many books are added, when the application is started, and when the last book is saved. In this case, you must be wondering why Spring. Controllers is 2. Because we’re counting ReadingListController and Spring Boot’s BasicErrorController.

Create a custom tracking warehouse

By default, trace information reported by the /trace endpoint is stored in the repository, capped at 100 entries. Once the warehouse is full, start removing old items to make room for new ones. This is fine in the development phase, but in a production environment, heavy traffic can cause trace information to be discarded before it can be viewed.

To avoid this problem, you can declare your InMemoryTraceRepository Bean and increase its capacity to more than 100. The following configuration classes can adjust the capacity to 1000 items:

    package readinglist; 
    import org.springframework.boot.actuate.trace.InMemoryTraceRepository; 
    import org.springframework.context.annotation.Bean; 
    import org.springframework.context.annotation.Configuration; 
    @Configuration 
    public class ActuatorConfig { 
     @Bean 
     public InMemoryTraceRepository traceRepository(a) { 
     InMemoryTraceRepository traceRepo = new InMemoryTraceRepository(); 
     traceRepo.setCapacity(1000); 
     returntraceRepo; }}Copy the code

Warehouse capacity has increased tenfold, and tracking information should last longer. However, busy enough, the application may discard the information before you can view it. This is a repository of memory storage, and it is important to keep the capacity from growing too much and affecting the memory usage of the application. In addition to the above methods, we can store those trace entries somewhere else — somewhere that doesn’t consume memory and can be stored for a long time. Simply implement Spring Boot’s TraceRepository interface:

    package org.springframework.boot.actuate.trace; 
    import java.util.List; 
    import java.util.Map; 
    public interface TraceRepository { 
     List<Trace> findAll(a); 
     void add(Map<String, Object> traceInfo); 
    }
Copy the code

As you can see, TraceRepository requires us to implement only two methods: a method that looks for all Trace objects stored, and a Map object that holds a Trace, which contains Trace information. As a demonstration, suppose we create a TraceRepository instance that uses a MongoDB database to store trace information. Save trace data to MongoDB

    package readinglist; 
    import java.util.Date; 
    import java.util.List; 
    import java.util.Map; 
    import org.springframework.beans.factory.annotation.Autowired; 
    import org.springframework.boot.actuate.trace.Trace; 
    import org.springframework.boot.actuate.trace.TraceRepository;
    import org.springframework.data.mongodb.core.MongoOperations; 
    import org.springframework.stereotype.Service; 
    @Service 
    public class MongoTraceRepository implements TraceRepository { 
     private MongoOperations mongoOps; 
     @Autowired 
     public MongoTraceRepository(MongoOperations mongoOps) {
     this.mongoOps = mongoOps; 
     } 
     @Override 
     public List<Trace> findAll(a) { 
     return mongoOps.findAll(Trace.class);
     } 
     @Override 
     public void add(Map<String, Object> traceInfo) { 
     mongoOps.save(new Trace(newDate(), traceInfo)); }}Copy the code

The findAll() method is straightforward, using MongoOperations injected to findAll Trace objects. The add() method is a little more interesting, creating a Trace object with the current time and a Map containing Trace information, then mongooperations.save () to save it. The only question is, where did MongoOperations come from?

To use MongoTraceRepository, we need to ensure that we have a MongoOperations Bean in the context of our Spring application. Thanks to Spring Boot’s startup dependencies and automatic configuration, you can do this by simply adding MongoDB startup dependencies. You need the following Gradle depends on: the compile (” org. Springframework. The boot: spring – the boot – starter – data – directing a “)

If you use Maven, you need the following dependencies:

<dependency> 
 <groupId>org.springframework.boot</groupId> 
 <artifactId>spring-boot-starter-data-mongodb</artifactId> 
</dependency>
Copy the code

Once this starter dependency is added, Spring Data MongoDB and its dependent libraries are added to the application’s Classpath. Spring Boot will automatically configure the beans you need to use the MongoDB database. MongoOperations is included in these beans. In addition, you need to ensure that the MongoDB server communicating with MongoOperations is running properly.