Skip to content

Commit

Permalink
Merge pull request #1439 from beckje01/ratpack-micrometer
Browse files Browse the repository at this point in the history
Micrometer
  • Loading branch information
beckje01 committed Apr 21, 2019
2 parents 60b0ff8 + 197f859 commit d9959ce
Show file tree
Hide file tree
Showing 14 changed files with 630 additions and 40 deletions.
Expand Up @@ -80,7 +80,7 @@
* import ratpack.handling.Context;
* import ratpack.registry.Registry;
* import ratpack.test.embed.EmbeddedApp;
*
* import ratpack.guice.Guice;
* import com.google.inject.AbstractModule;
* import com.google.inject.Inject;
Expand Down Expand Up @@ -148,7 +148,7 @@ public interface HandlerDecorator {

/**
* A handler decorator implementation that does not decorate the handler.
*
* <p>
* That is, it just returns the given handler.
*
* @return a handler decorator that does not decorate the handler
Expand All @@ -163,7 +163,7 @@ static HandlerDecorator noop() {
* As the {@code rest} argument encapsulates the application handlers, the returned handler should generally delegate to it (via {@link Context#insert(Handler...)}).
*
* @param serverRegistry the server registry
* @param rest the rest of the handlers of the application
* @param rest the rest of the handlers of the application
* @return a new handler
* @throws Exception any
*/
Expand Down
30 changes: 30 additions & 0 deletions ratpack-micrometer/ratpack-micrometer.gradle
@@ -0,0 +1,30 @@
/*
* Copyright 2018 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
*
* http://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.
*/

description = "Provides integration with Micrometer - https://micrometer.io/"

apply from: "$rootDir/gradle/javaModule.gradle"

ext {
micrometerVersion = "1.0.7"
}
dependencies {
compile project(":ratpack-core")
compile project(":ratpack-guice")

compile "io.micrometer:micrometer-core:$micrometerVersion"
compile "io.micrometer:micrometer-registry-prometheus:$micrometerVersion"
}
@@ -0,0 +1,28 @@
/*
* Copyright 2018 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
*
* http://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 ratpack.micrometer;

import java.util.Map;

public class MicrometerConfig {
public final String application;
public final Map<String, String> groups;

public MicrometerConfig(String application, Map<String, String> groups) {
this.application = application;
this.groups = groups;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2018 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
*
* http://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 ratpack.micrometer;

import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.prometheus.client.exporter.common.TextFormat;
import ratpack.handling.Context;
import ratpack.handling.Handler;
import ratpack.http.Status;

import javax.inject.Inject;

class PrometheusHandler implements Handler {
private final PrometheusMeterRegistry registry;

@Inject
public PrometheusHandler(PrometheusMeterRegistry registry) {
this.registry = registry;
}

@Override
public void handle(Context ctx) {
ctx.getResponse().contentType(TextFormat.CONTENT_TYPE_004).status(Status.OK).send(registry.scrape());
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2018 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
*
* http://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 ratpack.micrometer;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import ratpack.handling.HandlerDecorator;
import ratpack.micrometer.internal.DefaultRequestTimingHandler;

import static ratpack.handling.Handlers.chain;

public class PrometheusHandlerModule extends AbstractModule {

@Override
public void configure() {
bind(PrometheusHandler.class);
bind(RequestTimingHandler.class).to(DefaultRequestTimingHandler.class);

Multibinder.newSetBinder(binder(), HandlerDecorator.class)
.addBinding()
.toInstance((registry, rest) -> chain(chain(registry, chain ->
chain.get("metrics", PrometheusHandler.class))));
}
}

@@ -0,0 +1,92 @@
/*
* Copyright 2018 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
*
* http://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 ratpack.micrometer;

import com.google.inject.Provides;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.micrometer.core.instrument.binder.system.UptimeMetrics;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import ratpack.guice.ConfigurableModule;
import ratpack.micrometer.internal.DefaultRequestTimingHandler;

import javax.inject.Singleton;
import java.time.Duration;

public class PrometheusMicrometerModule extends ConfigurableModule<MicrometerConfig> {

@Override
public void configure() {
bind(RequestTimingHandler.class).to(DefaultRequestTimingHandler.class);
}

@Provides
@Singleton
PrometheusMeterRegistry providesPrometheus() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}

@Provides
@Singleton
CompositeMeterRegistry providesCompositeMeterRegistry() {
return new CompositeMeterRegistry();
}

@Provides
@Singleton
MeterRegistry providesMeterRegistry(PrometheusMeterRegistry prometheus,
CompositeMeterRegistry composite,
MicrometerConfig config) {
prometheus.config().meterFilter(new MeterFilter() {
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
return DistributionStatisticConfig.builder()
.percentilesHistogram(true)
.minimumExpectedValue(Duration.ofMillis(10).toNanos())
.maximumExpectedValue(Duration.ofSeconds(5).toNanos())
.build()
.merge(config);
}
});

prometheus.config().commonTags("application", config.application);

composite.add(prometheus);

new ClassLoaderMetrics().bindTo(composite);
new JvmMemoryMetrics().bindTo(composite);
new JvmGcMetrics().bindTo(composite);
new ProcessorMetrics().bindTo(composite);
new JvmThreadMetrics().bindTo(composite);
new LogbackMetrics().bindTo(composite);
new UptimeMetrics().bindTo(composite);

return composite;
}
}



@@ -0,0 +1,22 @@
/*
* Copyright 2018 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
*
* http://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 ratpack.micrometer;

import ratpack.handling.Handler;

@FunctionalInterface
public interface RequestTimingHandler extends Handler {
}
@@ -0,0 +1,78 @@
/*
* Copyright 2018 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
*
* http://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 ratpack.micrometer.internal;

import io.micrometer.core.instrument.MeterRegistry;
import ratpack.handling.Context;
import ratpack.micrometer.MicrometerConfig;
import ratpack.micrometer.RequestTimingHandler;

import javax.inject.Inject;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DefaultRequestTimingHandler implements RequestTimingHandler {
private final MeterRegistry meterRegistry;
private final MicrometerConfig metricsConfig;

@Inject
public DefaultRequestTimingHandler(MeterRegistry meterRegistry,
MicrometerConfig metricsConfig) {
this.meterRegistry = meterRegistry;
this.metricsConfig = metricsConfig;
}

@Override
public void handle(Context ctx) {
ctx.onClose((outcome) -> {
String statusCode = String.valueOf(outcome.getResponse().getStatus().getCode());
meterRegistry.timer("http.requests",
"status", statusCode,
"path", findPathGroup(outcome.getRequest().getPath()),
"method", outcome.getRequest().getMethod().getName().toLowerCase())
.record(outcome.getDuration().toNanos(), TimeUnit.NANOSECONDS);

meterRegistry.timer("http.server.requests", "status", statusCode).record(outcome.getDuration().toNanos(), TimeUnit.NANOSECONDS);
}
);
ctx.next();
}

private String findPathGroup(String requestPath) {
String tagName = "".equals(requestPath) ? "root" : requestPath;

for (Map.Entry<String, String> metricGrouping : metricsConfig.groups.entrySet()) {
Pattern pattern = Pattern.compile(metricGrouping.getValue());
Matcher match = pattern.matcher(requestPath);

if (requestPath.matches(metricGrouping.getValue())) {
tagName = metricGrouping.getKey();
}

if (match.groupCount() > 1) {
while (match.find()) {
for (int index = 1; index <= match.groupCount(); index++) {
tagName = tagName.replace("$" + index, match.group(index));
}
}
}
}
return tagName;
}
}

@@ -0,0 +1,22 @@
/*
* Copyright 2018 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
*
* http://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.
*/

/**
* Integration with <a href="https://micrometer.io/docs/concepts">Micrometer</a>.
* <p>
* See {@link ratpack.micrometer.PrometheusMicrometerModule} to get started.
*/
package ratpack.micrometer;

0 comments on commit d9959ce

Please sign in to comment.