Spring Boot Metrics integration with TICK Stack

Published on Jiffle Development Blog
at http://www.jiffle.net/blog/2016/11/24/spring-boot-metrics-with-influxdb/

Posted on November 24, 2016

At the moment Spring Boot does not have an Activator integration for InfluxDB and the TICK stack. It is being tracked as an enhancement as Issue 5688 and is currently scheduled for Spring Boot 1.5.0. Until there is an out-of-the-box implementation, this article explains what you need to do to get a Spring Boot application talking to InfluxDB database, using version 1.4.1.RELEASE of Boot.

The Writer is adapted from the commits referenced in that issue tracker, but the tests and the property-based configuration has been omitted for clarity.

System Load Graph Example

Assumptions

You need a Spring Boot application with the Actuator (spring-boot-starter-actuator) enabled, and, at the very least a working copy of InfluxDB, although running Chronograf is pretty much required for looking at the data in any meaningful way.

I’ve only included code and import statements for the bits that are key to the example. You’ll need to fill in the rest.

This approach will not work if you’re using the Dropwizard metrics capability within Spring Boot. That is automatically enabled if the Dropwizard metrics dependency io.dropwizard.metrics:metrics-core has been added to your build.

Dependencies

The only new dependency is to add the Java InfluxDB client. For Gradle add the following:

  compile 'org.influxdb:influxdb-java:2.4'

Spring Bean Configuration

There are two pieces of Spring configuration needed to make this work.

MetricsWriter

To get Spring to write metrics to Influx, you need to add an ExportMetricsWriter bean to your application config class, like in this example:

import org.influxdb.InfluxDB;
import org.influxdb.InfluxDBFactory;
import org.springframework.boot.actuate.autoconfigure.ExportMetricWriter;
import org.springframework.boot.actuate.metrics.writer.GaugeWriter;
...
	@Bean
	@ExportMetricWriter
	GaugeWriter influxMetricsWriter() {
		InfluxDB influxDB = InfluxDBFactory.connect( "http://localhost:8086",
				"root",
				"root");
		String dbName = "myMetricsDB";	// the name of the datastore you choose
		influxDB.createDatabase( dbName);
		InfluxDBMetricWriter.Builder builder = new InfluxDBMetricWriter.Builder( influxDB);
		builder.databaseName( dbName);
		builder.batchActions( 500);	// number of points for batch before data is sent to Influx
		return builder.build();
    }

Clearly in a production implementation, the database URL, username, password and datastore name would be made configurable.

Getting all JVM metrics

The configuration above will get bespoke metrics, but not the default JVM values, which can be very useful. In order to copy the PublicMetrics data to the writer, a second bean needs to be declared:

	@Bean 
	public MetricsEndpointMetricReader metricsEndpointMetricReader(MetricsEndpoint metricsEndpoint) { 
	  return new MetricsEndpointMetricReader(metricsEndpoint); 
	}

Writer class Implementation

This is adapted from the implementation in the issue tracker, with the only fix being to send the data with null as the InfluxDB Retention Policy, as the original value caused certain value names to cause the data to be rejected by Influx.

This also uses Lombok to reduce the boilerplate Java in the example. The Lombok @Builder annotation wan’t used as it is a different Builder pattern from the one generated by Lombok.

import java.util.concurrent.TimeUnit;

import org.influxdb.InfluxDB;
import org.influxdb.dto.Point;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.writer.GaugeWriter;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

public class InfluxDBMetricWriter implements GaugeWriter {

	private static final String DEFAULT_DATABASE_NAME = "metrics";
	private static final int DEFAULT_BATCH_ACTIONS = 500;
	private static final int DEFAULT_FLUSH_DURATION = 30;

	private final InfluxDB influxDB;
	private final String databaseName;

	private InfluxDBMetricWriter(Builder builder) {
		this.influxDB = builder.influxDB;
		this.databaseName = builder.databaseName;
		this.influxDB.createDatabase( this.databaseName);
		this.influxDB.enableBatch( builder.batchActions, builder.flushDuration,
				builder.flushDurationTimeUnit);
		this.influxDB.setLogLevel( builder.logLevel);
	}

	@Override
	public void set(Metric<?> value) {
		Point point = Point.measurement( value.getName())
				.time( value.getTimestamp().getTime(), TimeUnit.MILLISECONDS)
				.addField( "value", value.getValue())
				.build();
		this.influxDB.write( this.databaseName, null, point);
	}

	@Accessors(fluent = true, chain = true)
	@RequiredArgsConstructor
	public static class Builder {
		@NonNull private final InfluxDB influxDB;
		@Setter private String databaseName = DEFAULT_DATABASE_NAME;
		@Setter private int batchActions = DEFAULT_BATCH_ACTIONS;
		private int flushDuration = DEFAULT_FLUSH_DURATION;
		private TimeUnit flushDurationTimeUnit = TimeUnit.SECONDS;
		@Setter private InfluxDB.LogLevel logLevel = InfluxDB.LogLevel.BASIC;

		public Builder flushDuration( int flushDuration, TimeUnit flushDurationTimeUnit) {
			this.flushDuration = flushDuration;
			this.flushDurationTimeUnit = flushDurationTimeUnit;
			return this;
		}
		
		public InfluxDBMetricWriter build() {
			return new InfluxDBMetricWriter(this);
		}
	}
}