Skip to content

Commit

Permalink
Adding a prometheus exporter.
Browse files Browse the repository at this point in the history
  • Loading branch information
fbacchella committed Jan 1, 2025
1 parent 5bf7828 commit 581da7c
Show file tree
Hide file tree
Showing 7 changed files with 594 additions and 0 deletions.
99 changes: 99 additions & 0 deletions loghub-prometheus/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.loghub</groupId>
<artifactId>loghub-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>loghub-prometheus</artifactId>
<name>LogHub :: Prometheus exporter</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<prometheus.version>1.3.5</prometheus.version>
</properties>

<dependencies>
<dependency>
<groupId>fr.loghub</groupId>
<artifactId>loghub-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-exporter-common</artifactId>
<version>${prometheus.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-core</artifactId>
<version>${prometheus.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-instrumentation-jvm</artifactId>
<version>${prometheus.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-exporter-opentelemetry</artifactId>
<version>${prometheus.version}</version>
</dependency>
<dependency>
<groupId>fr.loghub</groupId>
<artifactId>loghub-core</artifactId>
<classifier>tests</classifier>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
<filters>
<filter>
<!-- Shading signed JARs will fail without this. http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
<filter>
<artifact>io.prometheus:*</artifact>
<excludes>
<exclude>lib/**</exclude>
</excludes>
</filter>
</filters>
<artifactSet>
<includes>
<include>io.prometheus:*</include>
</includes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package loghub.prometheus;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.prometheus.metrics.exporter.common.PrometheusHttpExchange;
import io.prometheus.metrics.exporter.common.PrometheusHttpRequest;
import io.prometheus.metrics.exporter.common.PrometheusHttpResponse;
import loghub.netty.http.HttpHandler;

class NettyPrometheusHttpExchange implements PrometheusHttpExchange {

private class HttpRequest implements PrometheusHttpRequest {
@Override
public String getQueryString() {
return uri.getQuery();
}

@Override
public Enumeration<String> getHeaders(String name) {
return Collections.enumeration(nettyRequest.headers().getAll(name));
}

@Override
public String getMethod() {
return nettyRequest.method().name();
}

/**
* Absolute path of the HTTP request.
*/
@Override
public String getRequestPath() {
return uri.getPath().replace("/prometheus/", "");
}
}

private class HttpResponse implements PrometheusHttpResponse {
@Override
public void setHeader(String name, String value) {
NettyPrometheusHttpExchange.this.headers.put(name, value);
}

@Override
public OutputStream sendHeadersAndGetBody(int statusCode, int contentLength) {
NettyPrometheusHttpExchange.this.status = statusCode;
NettyPrometheusHttpExchange.this.content = new ByteArrayOutputStream(contentLength);
return content;
}
}

private final HttpRequest request = new HttpRequest();
private final HttpResponse response = new HttpResponse();
private final URI uri;
private final Map<String, String> headers = new HashMap<>();
int status;
ByteArrayOutputStream content;

private final FullHttpRequest nettyRequest;
private final HttpHandler handler;
private final ChannelHandlerContext ctx;

NettyPrometheusHttpExchange(HttpHandler handler, FullHttpRequest nettyRequest, ChannelHandlerContext ctx) {
this.nettyRequest = nettyRequest;
this.uri = URI.create(nettyRequest.uri());
this.handler = handler;
this.ctx = ctx;
}

void copyHeader(HttpHeaders nettyHeaders) {
for (Map.Entry<String, String> e: headers.entrySet()) {
if (! nettyHeaders.contains(e.getKey() )) {
nettyHeaders.add(e.getKey(), e.getValue());
}
}
}

@Override
public PrometheusHttpRequest getRequest() {
return request;
}

@Override
public PrometheusHttpResponse getResponse() {
return response;
}

@Override
public void handleException(IOException e) {
try {
handler.exceptionCaught(ctx, e);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Override
public void handleException(RuntimeException e) {
try {
handler.exceptionCaught(ctx, e);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Override
public void close() {
// Nothing to do
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package loghub.prometheus;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;

import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.prometheus.metrics.exporter.opentelemetry.OpenTelemetryExporter;
import io.prometheus.metrics.model.registry.PrometheusRegistry;
import loghub.Helpers;
import loghub.netty.DashboardService;

/**
* Resolved the properties as described by <a href="https://prometheus.github.io/client_java/config/config/">client_java/config</a>
*/
public class PrometheusDashboardService implements DashboardService {

@Override
public String getPrefix() {
return "prometheus";
}

@Override
public List<SimpleChannelInboundHandler<FullHttpRequest>> getServices(Map<String, Object> properties) {
PrometheusRegistry registry = new PrometheusRegistry();
PrometheusMetricsEndpoint.Builder builder = PrometheusMetricsEndpoint.getBuilder();
if (properties.containsKey("withJvmMetrics")) {
builder.setWithJvmMetrics(Boolean.TRUE.equals(properties.remove("withJvmMetrics")));
}
if (Boolean.TRUE.equals(properties.remove("withOpenTelemetry"))) {
OpenTelemetryExporter.Builder openTelemetryExporterBuilder = OpenTelemetryExporter.builder();

openTelemetryExporterBuilder.registry(registry);
Map<String, Object> otlpProperties = Helpers.filterPrefix(properties, "openTelemetry");
Optional.ofNullable(otlpProperties.remove("endpoint")).map(String.class::cast).ifPresent(openTelemetryExporterBuilder::endpoint);
Optional.ofNullable(otlpProperties.remove("protocol")).map(String.class::cast).ifPresent(openTelemetryExporterBuilder::protocol);
Optional.ofNullable(otlpProperties.remove("interval")).map(Integer.class::cast).ifPresent(openTelemetryExporterBuilder::intervalSeconds);
Optional.ofNullable(otlpProperties.remove("timeoutSeconds")).map(Integer.class::cast).ifPresent(openTelemetryExporterBuilder::timeoutSeconds);
Optional.ofNullable(otlpProperties.remove("headers")).map(Map.class::cast).ifPresent(m -> fillMap(m, openTelemetryExporterBuilder::header));
Optional.ofNullable(otlpProperties.remove("resourceAttributes")).map(Map.class::cast).ifPresent(m -> fillMap(m, openTelemetryExporterBuilder::resourceAttribute));
Optional.ofNullable(otlpProperties.remove("serviceInstanceId")).map(String.class::cast).ifPresent(openTelemetryExporterBuilder::serviceInstanceId);
Optional.ofNullable(otlpProperties.remove("serviceNamespace")).map(String.class::cast).ifPresent(openTelemetryExporterBuilder::serviceNamespace);
Optional.ofNullable(otlpProperties.remove("serviceName")).map(String.class::cast).ifPresent(openTelemetryExporterBuilder::serviceName);
Optional.ofNullable(otlpProperties.remove("serviceVersion")).map(String.class::cast).ifPresent(openTelemetryExporterBuilder::serviceVersion);
openTelemetryExporterBuilder.buildAndStart();
}
builder.setRegistry(registry);
PrometheusMetricsEndpoint ps = builder.build();
return List.of(ps);
}

private void fillMap(Map<?, ?> m, BiConsumer<String, String> filler) {
m.forEach((k, v) -> filler.accept(k.toString(), v.toString()));
}

}
Loading

0 comments on commit 581da7c

Please sign in to comment.